仅将C ++编译器用于函数重载是不好的做法吗?
恕我直言的观点,是的,由于我喜欢这两种语言,因此我需要变得精神分裂以回答这一问题,但这与效率无关,而更像是安全性和惯用语言。
C面
从C的角度来看,让您的代码仅使用C函数重载就要求C ++非常浪费。除非您将其用于带有C ++模板的静态多态性,否则它是获得了如此琐碎的语法糖,以换成完全不同的语言。此外,如果您想将函数导出到dylib(可能或可能不切实际),那么对于所有带有名称纠缠符号的广泛使用而言,您将无法再进行实际操作。
C ++面
从C ++的角度来看,您不应该使用带有函数重载的C ++之类的C ++。这不是风格上的教条主义,而是与日常C ++的实际使用有关的一种。
如果您使用的C类型系统禁止复制诸如ctor之类的代码,那么您通常的C代码就只能做到合理合理且“安全”。 structs
。一旦您在C ++更为丰富的类型系统中工作,那么日常功能就变得非常有价值,memset
而memcpy
这些功能不会变成日常功能,您应该一直依靠它们。取而代之的是,它们通常像瘟疫一样避免使用,因为对于C ++类型,您不应将它们像原始位和字节那样对待,以进行复制和改组并释放。即使您的代码目前仅memset
在原语和POD UDT上使用诸如此类的东西,此刻任何人都将ctor添加到您使用的任何UDT中(包括仅添加需要一个成员的成员,例如std::unique_ptr
成员)针对此类函数或虚函数或任何此类函数,它将使您的所有常规C风格编码容易受到未定义行为的影响。从赫伯·萨特本人那里得到:
memcpy
并memcmp
违反类型系统。使用memcpy
复制的对象是像使用复印机赚钱。使用memcmp
比较对象就像通过计算豹子的斑点来比较它们。这些工具和方法似乎可以完成任务,但是它们太粗糙了,无法接受。C ++对象都是关于信息隐藏的(可以说是软件工程中最有利可图的原理;请参阅第11项):对象可以隐藏数据(请参阅第41项),并设计精确的抽象,以便通过构造函数和赋值运算符复制数据(请参阅第52到55项)。 。推销所有这些memcpy
都是严重违反信息隐藏的行为,并且通常会导致内存和资源泄漏(充其量),崩溃(更糟)或未定义的行为(最糟糕)-C ++编码标准。
如此之多的C开发人员都会对此表示反对,这是正确的,因为这种哲学仅在您使用C ++编写代码时才适用。如果您像在C ++那样构建的代码中一直使用函数,那么很可能会编写出非常有问题的代码,但是如果您使用C来编写代码,那将是非常好的事情。memcpy
。由于类型系统的差异,两种语言在这方面有很大的不同。看看这两个共同点的功能子集是很诱人的,并且相信它们可以像另一个一样使用,特别是在C ++方面,但是C +代码(或C--代码)通常比C和C ++代码。
同样,malloc
如果它可以直接调用可以抛出的任何C ++函数,则不应在C风格的上下文中使用它(这不意味着没有EH),因为那样的话,您的函数中就会隐含退出点在能够free
存储该内存之前,您不能有效地捕获编写C样式代码的异常。所以,只要你有基础,因为C ++有一个文件.cpp
扩展名或任何和它所有这些类型的东西一样malloc
,memcpy
,memset
,qsort
,等等,如果不是这样,那么它会进一步提出问题,除非它是仅适用于原始类型的类的实现细节,此时它仍需要进行异常处理以确保异常安全。如果你正在编写C ++代码的你,而不是想通常依靠RAII和使用之类的东西vector
,unique_ptr
,shared_ptr
,等,并尽可能避免一切正常的C风格的编码。
您可以使用C和X射线数据类型的剃须刀并使用其位和字节进行播放而不会造成团队附带损害的原因(尽管您仍然可以通过两种方式伤害自己)并不是因为C类型可以做到,但是由于它们永远无法做到。在将C的类型系统扩展为包括ctor,dtor和vtables之类的C ++功能以及异常处理的那一刻,所有惯用的C代码都将被呈现,远比当前危险得多,并且您将看到一种新的理念和思维方式的发展将鼓励完全不同的编码风格,正如您在C ++中所看到的那样,它现在考虑甚至对管理内存的类甚至使用与RAII兼容的资源(例如,unique_ptr
。这种心态并不是出于绝对的安全感。它是C ++专门针对某些异常情况(例如仅允许的情况)进行安全处理而演变而来的其在其类型系统中类的功能而开发的。
例外安全
同样,当您进入C ++领域时,人们会期望您的代码是异常安全的。人们可能会在将来维护您的代码,因为该代码已经用C ++编写并编译,并且只需std::vector, dynamic_cast, unique_ptr, shared_ptr
在代码直接或间接调用的代码中使用等等,就可以认为它是无害的,因为您的代码已经“应该”为C ++码。在这一点上,我们必须面对可能抛出的错误的机会,然后当您使用完全精美的C代码时,如下所示:
int some_func(int n, ...)
{
int* x = calloc(n, sizeof(int));
if (x)
{
f(n, x); // some function which, now being a C++ function, may
// throw today or in the future.
...
free(x);
return success;
}
return fail;
}
...它现在坏了。需要重写它以确保异常安全:
int some_func(int n, ...)
{
int* x = calloc(n, sizeof(int));
if (x)
{
try
{
f(n, x); // some function which, now being a C++ function, may
// throw today or in the future (maybe someone used
// std::vector inside of it).
}
catch (...)
{
free(x);
throw;
}
...
free(x);
return success;
}
return fail;
}
毛!这就是为什么大多数C ++开发人员会要求这样做的原因:
void some_func(int n, ...)
{
vector<int> x(n);
f(x); // some function which, now being a C++ function, may throw today
// or in the future.
}
上面是C ++开发人员通常会批准的符合RAII的异常安全代码,因为该函数不会泄漏没有哪一行代码会因以下原因触发隐式退出 throw
。
选择一种语言
您应该使用RAII,异常安全性,模板,OOP等拥抱C ++的类型系统和原理,或者拥抱C很大程度上围绕原始位和字节进行旋转。您不应该在这两种语言之间形成邪恶的婚姻,而应将它们分成不同的语言以区别对待,而不是将它们混淆在一起。
这些语言想嫁给你。通常,您必须选择一个,而不要同时约会和鬼混。或者,您也可以像我一样成为一夫多妻主义者,并且都可以结婚,但是在彼此相处时,您必须彻底改变自己的想法,并使他们彼此分开,以免彼此争斗。
二进制大小
出于好奇,我现在尝试使用我的免费列表实现和基准测试,并将其移植到C ++,因为我对此感到非常好奇:
[...]不知道C语言会是什么样子,因为我没有使用过C编译器。
...并且想知道二进制大小是否只会以C ++的形式膨胀。它要求我在所有地方都进行显式转换(这很糟糕(我更喜欢用C更好地编写诸如分配器和数据结构之类的底层代码)的一个原因),但只花了一分钟。
这只是将一个简单的控制台应用程序的MSVC 64位版本构建与不使用任何C ++功能的代码进行了比较,甚至没有运算符重载-只是使用C进行构建与使用(<cstdlib>
而不是<stdlib.h>
和)之间的区别。诸如此类的事情,但令我惊讶的是它对二进制大小的影响为零!
二进制文件9,728
在C语言中构建9,278
时为字节,在编译为C ++代码时同样为字节。我其实没想到。我以为像EH之类的东西至少会在那儿增加一点(认为至少会相差一百个字节),尽管大概它能够确定出不需要添加与EH相关的指令,因为我只是使用C标准库,没有任何异常。我在想什么就像RTTI一样,都会增加二进制大小。无论如何,看到它真是太酷了。当然,我不认为您应该从这一结果中概括一下,但这至少让我印象深刻。它也不会对基准产生任何影响,自然也是如此,因为我认为相同的结果二进制大小也意味着相同的结果机器指令。
也就是说,谁在乎上述安全性和工程问题的二进制大小?因此,再次选择一种语言并拥护其哲学,而不要试图将其混为一谈。这就是我的建议。
//
发表评论。如果有效,为什么不呢?