作为Linux(服务器端)开发人员,我不知道在哪里以及为什么应该使用C ++。
当我追求性能时,第一个和最后一个选择是C。
当“性能”不是主要问题时,Perl和Python等编程语言将是不错的选择。
我在这方面了解的几乎所有开源应用程序都是用C,Perl,Python,Bash脚本,AWK甚至PHP编写的,但是没有人使用C ++。
我不是在讨论GUI或Web应用程序之类的其他领域,而是在谈论Linux,CLI和守护程序。
使用C ++是否有令人满意的理由?
作为Linux(服务器端)开发人员,我不知道在哪里以及为什么应该使用C ++。
当我追求性能时,第一个和最后一个选择是C。
当“性能”不是主要问题时,Perl和Python等编程语言将是不错的选择。
我在这方面了解的几乎所有开源应用程序都是用C,Perl,Python,Bash脚本,AWK甚至PHP编写的,但是没有人使用C ++。
我不是在讨论GUI或Web应用程序之类的其他领域,而是在谈论Linux,CLI和守护程序。
使用C ++是否有令人满意的理由?
Answers:
当我要表演时,第一个和最后一个选择是C。
这就是您应该备份的地方。现在,我不能,在所有的,对服务器开发说话。也许确实没有令人信服的理由偏爱C ++而不是其他选择。
但是总的来说,使用C ++而不是其他语言的原因确实是性能。原因是C ++提供了一种抽象方法,与我所知道的所有其他语言不同,它在运行时没有性能开销。
这允许编写非常高效的代码,但仍具有很高的抽象级别。
考虑通常的抽象:虚函数,函数指针和PIMPL习惯用法。所有这些都依赖于在运行时由指针算法解决的间接。换句话说,这会产生性能成本(但是可能会很小)。
另一方面,C ++提供了一种间接机制,不会产生(性能)成本:模板。(这种优势是通过(有时极大地)增加了编译时间来弥补的。)
考虑一个通用排序函数的例子。
在C语言中,该函数qsort
采用一个函数指针,该指针实现了元素相对于彼此排序的逻辑。Java Arrays.sort
函数有多种变体。其中之一对任意对象进行排序,并要求Comparator
向其传递一个对象,该对象的工作方式与C中的函数指针非常相似qsort
。但是“本地” Java类型还有更多重载。它们每个都有自己的sort
方法副本–可怕的代码重复。
Java在这里说明了一般的二分法:您有代码重复,或者招致了运行时开销。
在C ++中,该sort
函数的工作原理与qsort
在C中非常相似,但有一个小但根本的区别:传递给函数的比较器是模板参数。这意味着它的调用可以被内联。比较两个对象不需要间接调用。在紧密的循环中(如此处的情况),这实际上可以产生很大的不同。
不足为奇的是,即使底层算法相同,C ++ sort
函数sort
也会胜过C语言。当实际的比较逻辑比较便宜时,这一点尤其明显。
现在,我并不是说C ++比C(或其他语言)更高效,也不是说它提供了更高的抽象度。它提供的是一个很高的抽象,同时又非常便宜,因此您通常不需要在高效和可重用的代码之间进行选择。
Arrays.sort
实现)。只有您失去了高度抽象的优势。这不是模板的特定缺点,通常是代码重复的缺点。此外,这往往无关紧要,因为在紧密循环中,通常总是加载相同的代码。
vector.push_back
为vector<int>
,另一个for vector<float>
,但是在使用a时vector<int>
,几乎没有理由将vector<float>
代码存储在指令缓存中。因此,我看不出这到底有多重要。单个模板实例化是经过高度优化的,并且通常比通用的“
我看到太多讨厌C ++的C程序员。我花了相当长的时间(数年)才慢慢了解它的优点和缺点。我认为最好的表达方式是:
更少的代码,没有运行时开销,更安全。
我们写的代码越少越好。在所有追求卓越的工程师中,这一点很快就变得显而易见。您只在一个地方修复了一个错误,却不是很多-您只表达一次算法,然后在许多地方重复使用,依此类推。希腊人甚至有一种说法可以追溯到古代的斯巴达人:你对此很明智”。事实的事实是,正确使用 C ++,您可以用比C少得多的代码来表达自己,而不会花费运行时间,同时比C更安全(即在编译时捕获更多错误)。
这是我的渲染器的一个简化示例:在三角形的扫描线上插入像素值时。我必须从X坐标x1开始并到达X坐标x2(从三角形的左侧到右侧)。在每个步骤中,在我经过的每个像素中,我都必须内插值。
当我插入到达像素的环境光时:
typedef struct tagPixelDataAmbient {
int x;
float ambientLight;
} PixelDataAmbient;
...
// inner loop
currentPixel.ambientLight += dv;
当我插入颜色时(称为“ Gouraud”底纹,其中“红色”,“绿色”和“蓝色”字段通过每个像素的步长值进行插入):
typedef struct tagPixelDataGouraud {
int x;
float red;
float green;
float blue; // The RGB color interpolated per pixel
} PixelDataGouraud;
...
// inner loop
currentPixel.red += dred;
currentPixel.green += dgreen;
currentPixel.blue += dblue;
当我在“ Phong”阴影中渲染时,我不再插值强度(ambientLight)或颜色(红色/绿色/蓝色)-我插值法线向量(nx,ny,nz),并且在每一步中,我都必须重新-根据插值法线向量计算照明方程:
typedef struct tagPixelDataPhong {
int x;
float nX;
float nY;
float nZ; // The normal vector interpolated per pixel
} PixelDataPhong;
...
// inner loop
currentPixel.nX += dx;
currentPixel.nY += dy;
currentPixel.nZ += dz;
现在,C程序员的第一个本能是““,编写三个插值的函数,然后根据设置模式调用它们”。首先,这意味着我遇到类型问题-我该如何处理?我的像素PixelDataAmbient吗?PixelDataGouraud?PixelDataPhong?哦,等等,高效的C程序员说,使用联合!
typedef union tagSuperPixel {
PixelDataAmbient a;
PixelDataGouraud g;
PixelDataPhong p;
} SuperPixel;
..然后,您有一个功能...
RasterizeTriangleScanline(
enum mode, // { ambient, gouraud, phong }
SuperPixel left,
SuperPixel right)
{
int i,j;
if (mode == ambient) {
// handle pixels as ambient...
int steps = right.a.x - left.a.x;
float dv = (right.a.ambientLight - left.a.ambientLight)/steps;
float currentIntensity = left.a.ambientLight;
for (i=left.a.x; i<right.a.x; i++) {
WorkOnPixelAmbient(i, dv);
currentIntensity+=dv;
}
} else if (mode == gouraud) {
// handle pixels as gouraud...
int steps = right.g.x - left.g.x;
float dred = (right.g.red - left.g.red)/steps;
float dgreen = (right.g.green - left.a.green)/steps;
float dblue = (right.g.blue - left.g.blue)/steps;
float currentRed = left.g.red;
float currentGreen = left.g.green;
float currentBlue = left.g.blue;
for (j=left.g.x; i<right.g.x; j++) {
WorkOnPixelGouraud(j, currentRed, currentBlue, currentGreen);
currentRed+=dred;
currentGreen+=dgreen;
currentBlue+=dblue;
}
...
您是否感到混乱进入?
首先,要使我的代码崩溃,只需输入一个错字,因为编译器永远不会在函数的“ Gouraud”部分中阻止我访问实际的“ .a”。(环境)值。没有被C类型系统捕获(即在编译期间)的错误,意味着该错误会在运行时显现出来,并且需要进行调试。您是否注意到我正在left.a.green
计算“ dgreen”?编译器肯定没有告诉您。
然后,到处都有重复- for
循环存在的次数与渲染模式一样多,我们一直在做“右减去左除以步长”。丑陋且容易出错。当我应该使用“ j”时,您是否注意到我在Gouraud循环中使用“ i”进行了比较?编译器再次保持沉默。
模式的if / else /阶梯怎么样?如果我在三周内添加了新的渲染模式怎么办?我会记得在所有代码中的所有“ if mode ==“”中处理新模式吗?
现在,将上述丑陋与这组C ++结构和一个模板函数进行比较:
struct CommonPixelData {
int x;
};
struct AmbientPixelData : CommonPixelData {
float ambientLight;
};
struct GouraudPixelData : CommonPixelData {
float red;
float green;
float blue; // The RGB color interpolated per pixel
};
struct PhongPixelData : CommonPixelData {
float nX;
float nY;
float nZ; // The normal vector interpolated per pixel
};
template <class PixelData>
RasterizeTriangleScanline(
PixelData left,
PixelData right)
{
PixelData interpolated = left;
PixelData step = right;
step -= left;
step /= int(right.x - left.x); // divide by pixel span
for(int i=left.x; i<right.x; i++) {
WorkOnPixel<PixelData>(interpolated);
interpolated += step;
}
}
现在来看这个。我们不再制作联合类型汤:每个模式都有特定的类型。他们通过继承基类(CommonPixelData
)来重用其常用的东西(“ x”字段)。模板使编译器创建(即代码生成)我们将用C语言编写的三个不同函数,但同时要严格限制类型!
我们在模板中的循环无法执行操作并无法访问无效字段-如果这样做,编译器将吠叫。
模板执行共同的工作(循环,每次都以“步长”递增),并且可以以不会导致运行时错误的方式进行。每种类型的插值(AmbientPixelData
,GouraudPixelData
,PhongPixelData
)与完成operator+=()
,我们将在结构增加-这基本上决定如何每种类型的插值。
您是否看到我们对WorkOnPixel <T>所做的工作?我们要为每种类型做不同的工作吗?我们简单地称呼模板专业化:
void WorkOnPixel<AmbientPixelData>(AmbientPixelData& p)
{
// use the p.ambientLight field
}
void WorkOnPixel<GouraudPixelData>(GouraudPixelData& p)
{
// use the p.red/green/blue fields
}
也就是说,要调用的函数是根据类型决定的。在编译时!
重述一下:
WorkOnPixel
版本,则C ++代码将比C更快,因为编译器将内嵌特定于类型的WorkOnPixel
模板特化呼叫!更少的代码,没有运行时开销,更安全。
这是否意味着C ++是语言的全部和全部?当然不是。您仍然必须权衡取舍。无知的人在应该编写Bash / Perl / Python脚本时会使用C ++。满足触发器要求的C ++新手将创建具有虚拟多重继承的深层嵌套类,然后才能停止它们并将其打包。在意识到这是不必要的之前,他们将使用复杂的Boost元编程。他们仍然会使用char*
,strcmp
和宏,而不是std::string
和模板。
但这无非是...观察与谁共事。没有语言可以保护您免受不称职的用户的侵害(没有,甚至没有Java)。
继续学习和使用C ++-只是不要过度设计。
RAII为胜利的宝贝。
严重的是,C ++中的确定性销毁使代码更清晰,更安全,而没有任何开销。
使用C ++是否有令人满意的理由?
模板和STL。您需要花费很少的构建时间(以及一些可能难以理解的错误消息)来换取许多有用的抽象和省力工具,而运行时性能却没有受到明显的影响(尽管二进制足迹可能更大)。
这需要一段时间来包装你的头左右(我花了几年的点击它之前),但一旦你这样做可以让生活很多简单。
C ++中的文本处理比C语言中的痛苦少几个数量级。
是。
如果您要寻找可执行的效率,那么您可以考虑使用C或C ++,因此我将重点介绍它。
甚至在模板不常见之前,我就早在1990年代中期就开始使用C ++而不是C讨论可执行文件,这有两个很简单的原因:对象多态和RAII。
我已经将多态C ++对象用于各种有趣的事情。例如,我正在一个嵌入式Linux系统上工作,该系统在OMAP和XScale ARM CPU上具有帧缓冲区覆盖。两种硬件体系结构具有不同的覆盖功能,并且具有非常不同的API。我使用一个通用的虚拟“ Overlay”基类公开了理想的叠加视图,然后编写了“ OmapOverlay”和“ XScaleOverlay”类,这些类在运行时进行了适当实例化,具体取决于运行代码的体系结构。
为了简化起见,RAII的想法是您在对象的构造函数期间或对象生命周期的稍后阶段分配连接到对象的资源,并且资源在对象的析构函数中被释放或释放。在C ++中,这确实很棒,因为自动变量的对象在超出范围时会被破坏。对于同样具备C和C ++能力的人,避免C ++中的资源和内存泄漏要容易得多。您也看不到太多的C ++代码,在函数的末尾,在调用之前有一个非常常见的标签C模因,在函数块中调用free()
,而goto
功能块中的各个跳转到那里。
我完全意识到,您可以使用C来完成所有这些事情-它要做的工作更多,代码行更多,并且最终得到的结果更加丑陋,而且通常很难理解。X服务器内部遍历了多态性代码,可是,它很丑陋又怪异,而且经常很难追踪。
我还使用GTK +和Clutter等GNOME技术进行了大量工作,所有这些技术都是使用GObject系统以C语言编写的。GObject就像C ++对象系统一样,它的封面很漂亮,所有丑陋的内部结构都暴露在外,它通常需要六行代码来完成单行C ++方法调用所要做的工作。我目前正在写一些东西ClutterActors
,尽管数学真的很有趣,但我一直在想:“在C ++中,这一切将更加简洁和可理解”。
我还常常想:“如果我用C ++而不是C编写代码,我会和妻子一起在客厅看MythBuster,而不是晚上9点坐在我的办公室。”
C ++与C一样快(有些东西更快,有些慢),并且它提供了更好的抽象和组织。类的工作方式与原始类型相似,从而无需记住即可使用大量代码。操作员重载和模板使编写代码的功能成为可能,如果数据表示形式发生更改,代码的功能会更好。异常可以简化错误处理。编译器可用于在编译时检查更多内容。
您为此付出的代价是一个相当讨厌的学习曲线,并且比我熟悉的大多数其他语言更容易犯一些细微的错误。
因此,我无法确定您现在正在做的事情是否值得您去学习。当然可以将Python或Perl与C结合使用,但是C ++在一个难以使用的程序包中同时提供了抽象和性能。
restrict
用于排除混叠优化,因此如何使事情更快?什么是“静态数组参数”?“样式”如何影响效果?
T (&arr)[n]
或std::array<T, n>
- 启用与C ++模板在功能上有所不同的任何东西,因为那里没有很多信息,因此必须进一步研究这一点。这对于智能指针是有意义的,绝对是一个很好的例子。如果在平等的竞争环境中进行编码,我们将不会使用异常,因此不会产生任何潜在的成本...但是,我怀疑您可能暗示了一旦第三方库进入画面后,会有很多假设处于危险之中。
我认为C ++是1990年代的语言,是过去时代的语言。
当时的规模很大,因为它以较低的性能成本提供了更高级别的语言结构和机制。它是开发从通讯录应用程序到航空电子软件的所有工具的通用工具,这激发了OO狂潮。OOP解决了饥饿和艾滋病,是的,我责怪C ++在1990年代末试图洗脑时,我第一次开始编程任何非OO语言都不值得学习。
既然硬件已经取得了如此多的进步,并且出现了更新的现代语言,我看不到C ++对于大多数应用程序编程仍然是一个相关的选择,除了需要大量抽象的计算密集型软件(游戏,物理模拟,CAD系统等)。 )。如果您使用C编写了一个紧凑的模块化引擎,并将更高级别的应用程序逻辑委派给一种简洁的脚本语言,则甚至可以避免后者。
如果您需要精通C语言,并且需要高级使用时,请使用不宣扬封装的现代语言来完成,而您可以通过指针自由更改每个字节。
根据Linus的说法,没有:
当我第一次查看Git源代码时,有两件事让我感到奇怪:1.纯C而不是C ++。不知道为什么。请不要谈论可移植性,它是BS。
你胡说八道。
C ++是一种可怕的语言。由于许多不合格的程序员使用它,使它变得更加可怕,以至于要用它产生完全和完全废话要容易得多。坦率地说,即使选择C 除了使C ++程序员不做任何事情,这本身就是使用C的巨大原因。
换句话说:C的选择是唯一明智的选择。我知道Miles Bader开玩笑地说“生气”,但这是真的。我得出的结论是,这将喜欢的项目是在C ++对C程序员的任何可能,我真的程序员 会喜欢走开,让他不来搞砸任何项目我参与用。
C ++导致非常糟糕的设计选择。您总是会开始使用该语言的“不错”库功能,例如STL和Boost,以及其他一些根本的废话,它们可能会“帮助”您编程,但会导致:
当它们不起作用时会产生无穷的痛苦(而且任何告诉我STL尤其是Boost稳定且可移植的人都充满了BS,甚至都不有趣)
效率低下的抽象编程模型,在过去的两年中,您注意到某种抽象不是很有效,但是现在您的所有代码都依赖于周围所有漂亮的对象模型,并且如果不重写应用程序就无法修复它。
换句话说,做一个好的,高效的,系统级的和可移植的C ++的唯一方法最终将自己限制在C语言中所有基本可用的东西上。而将项目限制在C语言中,则意味着人们不会拧,这也意味着您会得到很多真正了解底层问题并且不会因任何愚蠢的“对象模型”废话而搞砸的程序员。
因此,对不起,但是对于git这样的以效率为主要目标的情况,C ++的“优势”只是一个巨大的错误。我们还激怒了看不见的人,这只是一个很大的额外优势。
如果要使用C ++编写的VCS,请使用Monotone。真。他们使用“真实数据库”。他们使用“不错的面向对象的库”。他们使用“不错的C ++抽象”。坦率地说,由于所有这些设计决定对某些CS人士来说都如此吸引人,最终结果是一团糟且难以维护的混乱。
但是我敢肯定,您比git更想要它。
Linus
我认为没有任何令人信服的理由使用C ++。如果要进行OO编程,则可以改用Python并用C编写需要快速性能的部分。
编辑:还有其他语言可以很好地与C交互,因此,如果您不喜欢Python,则可以选择其他语言。
我只是从C切换到C ++,即使您不需要模板和OOP,我也认为收益是可观的。
令我惊讶的是,还没有人提到它,但是C ++向我们介绍了reference,它几乎解决了指针的所有问题和陷阱:
void ModifyVar(int& var)
{
var = 5;
}
int test = 4;
ModifyVar(test);
代替:
void ModifyVar(int * var)
{
*var = 5;
}
int test = 4;
ModifyVar(&test);
也更加安全,容易...而且没有价值传递的开销。
通常在何处以及为什么:
对于服务器端编程,您通常可以从编译或解释的多种不同语言中进行选择。通常,语言的选择取决于您或您的团队在哪个平台上最有效。或者,如果您还没有团队,那就是市场上技能的可用性。
附带说明一下,我真的不理解基于性能来决定使用C / C ++(仅),因为许多脚本语言都可以用C / C ++进行扩展。您将获得快速开发语言的好处以及将慢速部分迁移到C / C ++扩展的能力。当然,如果您在进行系统编程时每个操作都很重要,那是可以理解的,但是在大多数应用程序开发中,我是无法理解的。
C ++ vs Python vs Perl很难判断。这取决于项目和要求。
C ++从很早以前就有大量的实用程序,可以在许多平台上运行。但是开始遍历流只是将String传递给Integer并反向进行是很痛苦的。
另一方面,C ++对库的依赖项处理得很糟糕。一旦在GCC X或VC ++ Y中进行了编译,就无法依靠代码将在这些工具的下一版本中运行。在Windows上也一样,在Unix上也一样。
Perl从Unix世界获得了强大的功能,但特别是作为正则表达式工具。这是大多数时候使用的。Perl加上一些非常认真的工具,甚至Java也无法以正常方式做到这一点(请查看如何将文件上传到Web服务器),Perl就是“做到这一点”。
Python是简单,灵活和动态的语言。如此简单,您可以将整数发送给函数,脚本需要字符串,但可以得到结果!虽然出乎意料,但却是结果。因此程序员需要非常谨慎。空闲提供了一些调试功能,但是当您将TELNET连接到系统上,或者将SSH降低了三个级别时,如果您想查找问题,则调试器将不会在那里站着。但是,它可以快速完成一些出色的数学工作。
Java是一个由流行词,外来技术和流行词组成的生态系统,当您只想将文件上传到Web服务器时,就会发现只有在服务器具有JSP的情况下,您才能做到这一点。如果要调用系统库或诸如监视之类的系统功能,您会发现必须进行大量挖掘。也许是到达JNI并确定...然后您想...“为什么,上帝?”
除此之外,Java是用于商务套件和多线程的出色工具,我非常喜欢它。
快速编写程序并向您的简历显示“哦,我也知道技术”,而您想要成为的老板也很惊讶!即使,可能也不需要该技术...(好吧,伙计们,我讨厌Spring框架 ....)
选择语言时要牢记的一点是,使用该语言会带来什么好处,以及花费多长时间才能获得该语言。
像python和perl这样的语言之间的主要思想是,以更少的人工时间来做更多的事情,但是要花更多的cpu时间。实际上,您将花费更多的时间来编码python或perl脚本,而不是将其执行,但是您明白我的意思。
C / C ++的优点是它们速度很快,但是要花语法和强大的键入功能:您必须自己做很多事情,以使计算机不必在编译时选择它。
编写代码时,某些行比其他行要运行得多,而这些行会带来问题。另一方面,其余所有代码(您花了很多时间编写的代码)的执行频率要低得多。您可能已经听过,但这是臭名昭著的80/20规则,您将无法绕过该规则。
解决此问题的方法是使用一种更简单的语言(通过更简单的语言,我的意思是对开发人员更友好:更少的键入,懒惰的解释,大量预先存在的例程和内容,等等)来完成所有代码。
与使用C或C ++进行处理相比,这样做的速度如此之快,这会花费更多的脑部疼痛。
您的程序速度很慢,但是使用事件探查器,您可以隔离运行了80%的时间的零件,然后使用C或C ++进行处理。
通过以这种方式进行编程,您节省了很多时间,并且程序高效,快速,泄漏内存的机会更少,并节省了时间。
脚本语言被设计为在开发者方面,但是优化仍然是可能的。当然,您可以是设计模式魔术师,STL伏都教徒,甚至是轻率的战士,甚至还可以是Haskell的和尚。但是语言使我们与计算机对话,语言不是让我们成为计算机!
我使用的C ++称为带有类的C!