當前位置:
首頁 > 知識 > C++ 20 還未發布,就已涼涼?

C++ 20 還未發布,就已涼涼?

一個月前,C++ 標準委會於美國 San Diego 舉辦了有史以來規模最大的一次會議(180 人蔘會),特地討論了 C++ 新標準即

C++ 20

將確認添加以及有可能添加的特性。

C++ 20 還未發布,就已涼涼?


而如今距離 C++ 20 標準的正式發布不足一個月,C++ 20 卻慘遭國內外開發者嫌棄。對此,更有一位來自國外的遊戲開發者通過使用畢達哥拉斯三元數組示例發萬字長文批判新版本的到來並沒有解決最關鍵的技術問題。這到底是怎麼一回事?接下來,我們將一窺究竟。

C++ 20 還未發布,就已涼涼?

作者 | Aras Pranckevi?ius

譯者 | 蘇本如

責編 | 屠敏

出品 | CSDN(ID:CSDNNews)

以下為譯文:

首先聲明,本文較長,但我想表達的主要觀點是:

  1. C++編譯時間很重要;
  2. 非優化的build性能很重要;
  3. 認知負荷很重要。我在這裡不詳細闡述,但是如果一種編程語言或一個庫讓我覺得自己很愚蠢,那麼我就不太可能使用它或喜歡它。C++很多地方就讓我有這樣的感覺。

Facebook工程師、Microsoft Visual C++開發者Eric Niebler在他的博文」Standard Range」(http://ericniebler.com/2018/12/05/standard-ranges/)介紹了C++ 20的Range特性在最近的Twitter遊戲開發的各種應用,很多地方都表達了對於Modern C++現狀的「不喜歡」。

我也在這裡表達了類似的看法:


使用C++ 20 Range和其他特性來計算畢達哥拉斯三元組的例子聽起來很可怕。是的,我明白Range在這裡是有用的,結果也是對的……。但是,這仍然是一個可怕的例子!沒有人想要這樣的代碼?!

感覺有點失控了。這裡我要為引用Eric的博文向他道歉,我的悲觀看法大部分是基於最近的C++的現狀,Twitter上的「Bunch of angry gamedev」已經採用了Boost庫,Geometry rationale也同樣這樣做了,同樣的事情發生在C++生態系統中也有十幾次。

但你知道,Twitter上無法表達太多細節,所以我在這裡展開一下!

使用C++20 Ranges來計算畢達哥拉斯三元數組

這裡是Eric的博文里的完整例子:

// A sample standard C++20 program that prints
// the first N Pythagorean triples.
#include <iostream>
#include <optional>
#include <ranges> // New header!
using namespace std;
// maybe_view defines a view over zero or one
// objects.
template<Semiregular T>
struct maybe_view : view_interface<maybe_view<T>> {
maybe_view() = default;
maybe_view(T t) : data_(std::move(t)) {
}
T const *begin() const noexcept {
return data_ ? &*data_ : nullptr;
}
T const *end() const noexcept {
return data_ ? &*data_ + 1 : nullptr;
}
private:
optional<T> data_{};
};
// "for_each" creates a new view by applying a
// transformation to each element in an input
// range, and flattening the resulting range of
// ranges.
// (This uses one syntax for constrained lambdas
// in C++20.)
inline constexpr auto for_each =
[]<Range R,
Iterator I = iterator_t<R>,
IndirectUnaryInvocable<I> Fun>(R&& r, Fun fun)
requires Range<indirect_result_t<Fun, I>> {
return std::forward<R>(r)
| view::transform(std::move(fun))
| view::join;
};
// "yield_if" takes a bool and a value and
// returns a view of zero or one elements.
inline constexpr auto yield_if =
[]<Semiregular T>(bool b, T x) {
return b ? maybe_view{std::move(x)}
: maybe_view<T>{};
};
int main() {
// Define an infinite range of all the
// Pythagorean triples:
using view::iota;
auto triples =
for_each(iota(1), [](int z) {
return for_each(iota(1, z+1), [=](int x) {
return for_each(iota(x, z+1), [=](int y) {
return yield_if(x*x + y*y == z*z,
make_tuple(x, y, z));
});
});
});
// Display the first 10 triples
for(auto triple : triples | view::take(10)) {
cout << "("
<< get<0>(triple) << ","
<< get<1>(triple) << ","
<< get<2>(triple) << ")" << "
";
}
}

Eric的這篇博文可以追溯到他幾年前發的另一篇博文(http://ericniebler.com/2014/04/27/range-comprehensions/),這是對Bartosz Milewski寫的這篇博文「Getting Lazy with C++」(https://bartoszmilewski.com/2014/04/21/getting-lazy-with-c/)的回應,Eric在那篇博文給出了用一個簡單C函數來列印出前N個畢達哥拉斯三元數組的例子:

void printNTriples(int n)
{
int i = 0;
for (int z = 1; ; ++z)
for (int x = 1; x <= z; ++x)
for (int y = x; y <= z; ++y)
if (x*x + y*y == z*z) {
printf("%d, %d, %d
", x, y, z);
if (++i == n)
return;
}
}

從這個示例代碼可以看出一些問題:


這段代碼看起來沒有問題,如果您不需要修改它或重用它的話。但是如果你想對它做更多的事問題就出來了,比如你不僅僅想列印出這些三元數組,而且想基於這些三元數組畫一些三角形呢?或者如果你想在其中一個數字達到100時立即停止計算呢?

列表解析(list Comprehension)和延後計算(lazy evaluation)似乎可以作為解決這些問題的方法。事實上,它根本解決不了這些問題,因為C++語言不具備像Haskell或其他語言那樣的內置功能。C++ 20在這方面會有更多內置的東西,如Eric的博文指出的內容。這部分容我稍後再談。

使用Simple C++來計算畢達哥拉斯三元數組

所以,讓我們回到簡單的(正如Bartosz所說的「只要你不需要修改或重用」)C/C++方式解決這個問題。下面是一個完整的程序用來列印前100個畢達哥拉斯三元數組:

// simplest.cpp
#include <time.h>
#include <stdio.h>
int main()
{
clock_t t0 = clock();
int i = 0;
for (int z = 1; ; ++z)
for (int x = 1; x <= z; ++x)
for (int y = x; y <= z; ++y)
if (x*x + y*y == z*z) {
printf("(%i,%i,%i)
", x, y, z);
if (++i == 100)
goto done;
}
done:
clock_t t1 = clock();
printf("%ims
", (int)(t1-t0)*1000/CLOCKS_PER_SEC);
return 0;
}

我們可以編譯它:clang simpest.cpp-o outsimpest。編譯需要0.064秒,生成8480位元組的可執行文件,該文件在2毫秒內運行並列印數字(使用的機器為2018產的 MacBookPro;Core i9 2.9GHz CPU;xcode 10 clang):

(3,4,5)
(6,8,10)
(5,12,13)
(9,12,15)
(8,15,17)
(12,16,20)
(7,24,25)
(15,20,25)
(10,24,26)
...
(65,156,169)
(119,120,169)
(26,168,170)

但是等等!這是一個默認的非優化(「debug」)build;讓我們用這個命令來做一次優化(「release」)build:clang simplest.cpp -o outsimplest -O2。它需要0.071進行編譯,生成相同大小(8480b)的可執行文件,並在0毫秒內運行(在clock()的計時器精度內)。

正如Bartosz指出的那樣,這裡的演算法不是「可重用的」,因為它將處理過程和輸出結果混雜在一起了。這是不是一個問題不在本文的討論範圍之內(我個人認為,「可重用性」或「不惜一切代價避免重複」都被高估了)。讓我們假設這是一個問題,事實上,我們想要的是一個只返回前n個三元數組的「東西」,而不需要對它們做任何額外操作。

我可能會寫的是一段最簡單的代碼 - 調用它,返回下一個三元數組 - 看起來像下面這樣:

// simple-reusable.cpp
#include <time.h>
#include <stdio.h>
struct pytriples
{
pytriples() : x(1), y(1), z(1) {}
void next()
{
do
{
if (y <= z)
++y;
else
{
if (x <= z)
++x;
else
{
x = 1;
++z;
}
y = x;
}
} while (x*x + y*y != z*z);
}
int x, y, z;
};
int main()
{
clock_t t0 = clock();
pytriples py;
for (int c = 0; c < 100; ++c)
{
py.next();
printf("(%i,%i,%i)
", py.x, py.y, py.z);
}
clock_t t1 = clock();
printf("%ims
", (int)(t1-t0)*1000/CLOCKS_PER_SEC);
return 0;
}

上面這段代碼在幾乎相同的時間編譯和運行;Debug版本的可執行文件將變大168位元組;Release版本的可執行文件大小不變。

這裡我定義了一個名為pytriples的結構體(struct pytriples),通過對它的next()函數的每次調用都會得到下一個有效的三元數組;調用方可以對返回結果做任意處理。在這裡,我只是調用了100次next()函數,然後將每次返回的三元數組列印出來。

然而,上段代碼在功能上實現了原始示例中的三重嵌套for循環所做的工作。但事實上,它變得不那麼清晰了,至少對我來說是這樣,雖然我很清楚在一些分支和對整數的簡單操作上它是如何做到的,但是搞不清在更高層次上它是怎麼做的。

如果C++有一個類似「coroutine」的概念,那麼它有可能將三元數組生成器的代碼變得與原始嵌套for循環一樣清晰,這樣實現沒有任何「問題」,就像Jason Meisel在博文「Ranges, Code Quality, and the Future of C++」指出的那樣。代碼看起來是這樣的(它只是假設的寫法,因為coroutines不是任何C++ 標準的一部分):

generator<std::tuple<int,int,int>> pytriples()
{
for (int z = 1; ; ++z)
for (int x = 1; x <= z; ++x)
for (int y = x; y <= z; ++y)
if (x*x + y*y == z*z)
co_yield std::make_tuple(x, y, z);
}

回到C++ Ranges

用C++ 20的Ranges來實現三元數組的寫法會更清楚嗎?讓我們再看看Eric的博文,這是代碼的主要部分:

auto triples =
for_each(iota(1), [](int z) {
return for_each(iota(1, z+1), [=](int x) {
return for_each(iota(x, z+1), [=](int y) {
return yield_if(x*x + y*y == z*z,
make_tuple(x, y, z));
});
});
});

兩種方式都有爭議。我認為上面的「coroutines」方法更清晰。使用C++的Lambda表達式,或者使用看起來更聰明些的標準C++方式,都有些累贅。如果讀者習慣了命令式編程風格,那麼多個Return返回就顯得不常見了,但可能有人會習慣它。

也許你可以眯起眼睛說這是一個可以接受的好的寫法。

但是,我不相信像我們一樣的沒有C++博士學位的凡人能夠寫出下面的實用工具供上面代碼使用:

template<Semiregular T>
struct maybe_view : view_interface<maybe_view<T>> {
maybe_view() = default;
maybe_view(T t) : data_(std::move(t)) {
}
T const *begin() const noexcept {
return data_ ? &*data_ : nullptr;
}
T const *end() const noexcept {
return data_ ? &*data_ + 1 : nullptr;
}
private:
optional<T> data_{};
};
inline constexpr auto for_each =
[]<Range R,
Iterator I = iterator_t<R>,
IndirectUnaryInvocable<I> Fun>(R&& r, Fun fun)
requires Range<indirect_result_t<Fun, I>> {
return std::forward<R>(r)
| view::transform(std::move(fun))
| view::join;
};
inline constexpr auto yield_if =
[]<Semiregular T>(bool b, T x) {
return b ? maybe_view{std::move(x)}
: maybe_view<T>{};
};

也許上面代碼對某些人來說就像母語那樣易讀,但對我來言,感覺就像有人告訴我「Perl的可讀性太好,而Brainfuck的可讀性太不好了,讓我們平衡一下吧」。但是我還是覺得很難讀,儘管我用C++編程已經20年了,可能是我太蠢吧。

是的,這裡的maybe_view, for_each, yield_if都是「可重用組件」,這些組件可以移到一個庫中。 接下來我就要討論這個問題。

C++的「一切都是Library」帶來的問題

問題至少有兩個:1)編譯時間;2)非優化的運行時性能。

我還是以同樣的畢達哥拉斯三元數組的例子為例,這些問題對於C++的許多其他特性也存在,因為它們也是作為Library的一部分來實現的。

實際上C++ 20還沒有發布,我用了當前最接近C++20 Range的Range-v3(Eric Niebler自己寫的),來實現標準的「畢達哥拉斯三元數組」,並做了快速編譯測試。代碼如下:

// ranges.cpp
#include <time.h>
#include <stdio.h>
#include <range/v3/all.hpp>
using namespace ranges;
int main()
{
clock_t t0 = clock();
auto triples = view::for_each(view::ints(1), [](int z) {
return view::for_each(view::ints(1, z + 1), [=](int x) {
return view::for_each(view::ints(x, z + 1), [=](int y) {
return yield_if(x * x + y * y == z * z,
std::make_tuple(x, y, z));
});
});
});
RANGES_FOR(auto triple, triples | view::take(100))
{
printf("(%i,%i,%i)
", std::get<0>(triple), std::get<1>(triple), std::get<2>(triple));
}
clock_t t1 = clock();
printf("%ims
", (int)(t1-t0)*1000/CLOCKS_PER_SEC);
return 0;
}

使用命令:clang ranges.cpp -I. -std=c++17 -lc++ -o outranges 來編譯版本為post-0.4.0 version (9232b449e44 on 2018 Dec 22)的代碼。結果如下:

  • 編譯時間2.92秒
  • 生成的可執行文件為219千位元組
  • 運行時間為300毫秒

是的,以上是一個非優化的build的測試結果。優化的build的結果如下:

  • 編譯命令:clang ranges.cpp -I. -std=c++17 -lc++ -o outranges -O2
  • 編譯時間3.02秒
  • 生成的可執行文件為13.976千位元組
  • 運行時間為1毫秒

可以看出,runtime性能提升,可執行文件變小,但編譯時間的問題不變。

下面再繼續展開:

編譯時間是C++的一個大問題

這個簡單示例的編譯時間比「Simple C++」版本 多耗時2.85秒。

千萬不要認為「少於3秒」是一個很短的時間 - 這絕對不是。在3秒鐘內,一個現代的CPU可以完成天文數量的操作。例如,一個完整的實際資料庫引擎(SQlite)擁有22萬行代碼, 用clang命令做debug build,在我的機器上編譯時間是0.9秒。你能想像編譯微不足道的5行示例代碼竟然比編譯一個完整資料庫引擎要慢三倍嗎?!

只要我的編程工作用到的的代碼庫不是太小,C++編譯時間就是我的一個痛苦來源。如果你不相信,你可以試著編譯一個廣泛使用的大代碼庫(如:Chromium,Clang/LLVM,UE4等等)看看需要多少時間。在我真正希望C++能解決的問題,「解決編譯時間」可能位列第一,這個次序一直都是。然而,似乎C++社區並不認為這是個問題,每次版本的更新會把更多的東西放到頭文件中,更多的東西放到模板代中,而這些模板也是頭文件的一部分。

在很大程度上,這是由於C++從C語言繼承下來的原始的#include命令導致的。然而C語言往往只在頭文件中有結構聲明和函數原型,但是C++中經常需要將整個模板類/函數放在頭文件中。

range-v3的源代碼有1.8兆位元組,全部在頭文件中!因此,儘管「使用range輸出100個三元數組」的示例只有30行,但在處理完頭文件之後,需要編譯10.2萬行代碼。而「簡單C++」的示例,在所有的預處理之後,只有720行代碼。

你會說使用預編譯頭文件和/或預編譯模塊可以解決這個問題!這話有點道理, 讓我們試驗一下:

  • 首先將range的頭文件加入預編譯頭文件pch.h中 (pch:h: #include <range/v3/all.hpp>); 這樣在源代碼中只需要include pch.h, 而不需要再include range頭文件。
  • 接著生成預編譯頭文件PCH: clang -x c++-header pch.h -I. -std=c++17 -o pch.h.pch;
  • 然後使用PCH編譯: clang ranges.cpp -I. -std=c++17 -lc++ -o outranges -include-pch pch.h.pch)

這時候的編譯時間變成了2.24秒。

所以使用PCH確實可以節省大約0.7秒的編譯時間。結果仍然比簡單C++的編譯時間多了2.1秒。

非優化的Build性能的重要性

「ranges」示例的運行時的性能慢了150倍。慢2-3倍我覺得勉強可以接受,「慢十倍以上」意味著「沒法用」,慢了一百倍多?殺了我吧。

對於解決實際問題的實際代碼,慢兩個數量級意味著它根本無法正常工作。就拿我所從事的視頻遊戲行業;在實際工作中,慢兩個數量級意味著對遊戲引擎或工具的debug build將無法滿足實際工作的需要(性能將遠遠滿足不了遊戲的互動要求)。對於有些行業,它允許你在一組數據上運行一個程序,然後慢慢等結果。那麼調試過程多花費10-100倍的時間,可能僅僅有點「煩人」。但是如果某個功能必須是互動式的,那麼它就不僅僅是「煩人」了,而是「不可用」了 。比如。如果某個遊戲的界面以每秒兩幀的速度渲染,那你就根本無法玩這個遊戲。

是的,優化的build(clang -O2)的運行時性能與簡單C++一樣, C++的「zero-cost abstraction(零成本抽象)」確實起作用。前提要你不關心編譯時間,而且你有一個優化編譯器。

但是在選中優化代碼選項時的debug還是很困難!當然,可能有些人認為這是一種非常有用的技巧……類似於騎獨輪車可能教會你一種重要的平衡技巧。有些人喜歡它,甚至非常擅長它!但大多數人不會選擇獨輪車作為他們的主要交通工具,就像大多數人儘可能地避免在用選中優化代碼選項的情況下來調試代碼。

Arseny Kapoulkine在Youtube上有個很棒的視頻「Optimizing OBJ loader」,他也遇到了「Debug build太慢」的問題,他通過去掉一些stl bit庫文件,使其運行速度提高了10倍。同時這樣做也讓編譯速度更快和調試更容易,原因是微軟的STL實現特別喜歡深度嵌套的函數調用。

這並不是說「STL必然是壞的」, 編寫一個在非優化的build模式下不會變慢10倍的STL實現是有可能的(如EASTL或Libc++)。但是無論出於何種原因,微軟的STL速度非常慢。由於它過度依賴於inlining.

作為語言的使用者,我不在乎它是誰的錯!我只知道的「STL在調試中太慢」,我要麼將它解決掉,要麼尋找替代方案(例如不使用STL,重新實現我需要的bits庫,或者完全不再使用C++)。

其他語言怎麼樣呢?

下面簡單介紹一下C#是如何實現「延後計算方式生成畢達哥拉斯三元數組」的。完整的C#源代碼如下:

using System;
using System.Diagnostics;
using System.Linq;
class Program
{
public static void Main()
{
var timer = Stopwatch.StartNew();
var triples =
from z in Enumerable.Range(1, int.MaxValue)
from x in Enumerable.Range(1, z)
from y in Enumerable.Range(x, z)
where x*x+y*y==z*z
select (x:x, y:y, z:z);
foreach (var t in triples.Take(100))
{
Console.WriteLine($"({t.x},{t.y},{t.z})");
}
timer.Stop();
Console.WriteLine($"{timer.ElapsedMilliseconds}ms");
}
}

比較起來,C#的可讀性比C++要好一點。下面是C#的寫法

var triples =
from z in Enumerable.Range(1, int.MaxValue)
from x in Enumerable.Range(1, z)
from y in Enumerable.Range(x, z)
where x*x+y*y==z*z
select (x:x, y:y, z:z);

下面是 C++的寫法:

auto triples = view::for_each(view::ints(1), [](int z) {
return view::for_each(view::ints(1, z + 1), [=](int x) {
return view::for_each(view::ints(x, z + 1), [=](int y) {
return yield_if(x * x + y * y == z * z,
std::make_tuple(x, y, z));
});
});
});

我知道上面哪一個可讀性更好。你怎麼看?為公平起見,我們看看用C# LINQ的實現(代碼如下):

var triples = Enumerable.Range(1, int.MaxValue)
.SelectMany(z => Enumerable.Range(1, z), (z, x) => new {z, x})
.SelectMany(t => Enumerable.Range(t.x, t.z), (t, y) => new {t, y})
.Where(t => t.t.x * t.t.x + t.y * t.y == t.t.z * t.t.z)
.Select(t => (x: t.t.x, y: t.y, z: t.t.z));

編譯上面這個C#代碼需要多少時間?我在Mac上,所以我將使用Mono編譯器(它本身是用C#寫的),版本5.16。編譯命令mcs linq.cs需要0.20秒。相比之下,編譯相同功能的的「簡單C#」版本需要0.17秒。

所以這個延後計算的LINQ寫法為編譯器增加了額外的0.03秒。相比之下,C++的寫法增加了額外的3秒,增加的編譯時間超過了100倍!

所以,不同語言的特性不同,決定了你得到的結果也不同。而不僅僅是「編譯器費勁讀完10萬行代碼」問題。

但你不能忽略你不喜歡的部分嗎?

是的,在某種程度上來說,你可以。

例如,過去在我們Unity,我們有一句玩笑,「你敢向代碼庫中添加Boost你就等著被開除吧」。我想這不是真的,因為我發現去年某個時候Boost.asio被加進了代碼庫,自那以後我對於編譯速度極慢抱怨了很多次,因為include <asio.h>頭文件會導致include 整個的<windows.h>頭文件,而include這個頭文件會導致「 macro name hijack」錯誤

一般來說,我們努力避免使用大多數STL。容器我們也用我們自己的,這樣做的原因和創建EASTL是一樣的:

  • 不同平台/不同編譯器下的行為更加一致,
  • 非優化Build的性能更好,
  • 更好地與我們自己的memory allocator和Tracking相集成,
  • 其他一些容器,純粹是出於性能原因(STL的unordered_map設計上快不起來,因為它要求是separately chained;而我們自己的哈希表使用開放定址)。
  • 大部分的標準庫功能我們根本用不上。

然而。要說服每一位新員工(尤其是那些剛剛大學畢業的初級員工)需要時間,不能僅僅因為它被稱為「Modern C++」就並不意味著它會更好, 也不能因它是C就意味著它很難理解、使用或者有很多bug。

就在幾周前的工作中,我試圖讀懂我們自己寫的一段代碼,但是由於代碼太複雜我實在理解不了它。我正在百無聊賴時,一位(初級)程序員過來問我,為什麼我看起來像要焉了一樣,我告訴他「我看不懂這段代碼,對我來說它太複雜了。」他的第一反應是「哦,是舊式的C代碼?」「不,恰恰相反!」我回答。因為我正在看的代碼是某種template metaprogramming。這位初級程序員還沒有接觸到大型代碼庫,既沒有用過C也沒有用過C++,但有些東西已經讓他相信「難以理解的」肯定是C代碼。我懷疑現在的大學班級里都會直接說「C不好」,而不會解釋到底是怎麼回事;但這確實給未來的年輕程序員留下了這樣的印象。

所以,我當然可以忽略C++中我不喜歡的那部分。但是,教育我現在的同事是很煩人的,因為許多人認為「現代的肯定就更好」或者「標準庫就肯定比我們自己寫的任何東西都好」。

C++為什麼會這樣?

我不太清楚。誠然,他們確實有一個非常複雜的問題要解決,那就是「如何在保持一種語言與過去幾十年的版本100%向下兼容的同時更新它」。再加上C++想要服務各種不同經驗水平的人群,它不變得複雜才怪。

某種程度來講,C++標準委員會和它的生態圈,對它的複雜性的關注更多則在於炫耀或證明自己的價值。

我記得大約16年前,我還是個中年人,非常迷戀Boost。那時互聯網上有一個玩笑,一個C/C++程序員開發了個東西出來,人們就會驚嘆「哇,你竟然能用C/C++寫,太牛了!」,不會有人問你為什麼要用它。

類似於一級方程式賽車或三頸吉他,你對它們印象深刻吧!當然啦,製造出這樣的東西不可思議。然而掌握它需要大量的技能。你會發現在99%的情況下,完全用不上它們。

Christer Ericson在這裡說得很好:


程序員的目標是按時按預算交付開發的產品。它不是「產生代碼」。我個人認為,大多數Modern C++的支持者對源代碼的重視超過對編譯時間、可調試性、對新概念的認知負荷和額外的複雜性、項目需求等等。而後者才是最重要的。

關注C++和STL現狀的人當然可以加入進來去改進它。有些人已經這樣做了,有些人太忙了沒能時間做這些,有些人拋棄了部分STL而建立了自己的並行庫(如EASTL)。有些人認為C++不值得拯救,還不如自己另起爐灶,用新的語言來代替它(如Jai,Rust 和subsets of C#)。

接受反饋,並給出反饋

當「網上一群憤怒的人」說你的工作是眾所周知的垃圾時,我知道這讓人很難受。我正在開發可能是最受歡迎的遊戲引擎,擁有數百萬的用戶,而他們中的一些人喜歡直接或間接地指責「它太差勁了」。這讓人很難受,我們花了這麼多的心思和精力的遊戲,卻有人過來說我們都是白痴而我們的遊戲很垃圾。真是讓人傷心!

對於任何工作在C++、STL或任何其他廣泛使用的技術上的人來說,他們的遭遇可能是相同的。他們在某件事上工作了很多年,然後一群憤怒的網路用戶來了,說你費盡心血的成果是垃圾。

自然而然地你會為自己辯護。但通常這不是最有建設性的。

忽略網上那些幸災樂禍的鍵盤俠們,網上大多數的抱怨背後確實有實際的問題。有些措辭可能很差,或者誇張,或者抱怨的人沒有考慮其他可能的觀點,但是無論如何,抱怨背後的問題確實存在。

每當我聽到有人抱怨我所做的東西時,我會試著不去想這是「我」和「我做的東西」,我會站在他們的立場來想,他們遇到了什麼問題,他們想解決的問題是什麼。任何軟體/庫/語言的目的都是幫助用戶解決他們的問題。這可能是解決他們問題的一個完美工具,或者一個「大概可以」的工具,或者一個非常糟糕的工具。

  • 「我儘力了,但看起來我的工作不擅長解決你的問題」是一個非常有效的結果!
  • 「我儘力了,但不了解您的需求,讓我看看如何解決這些需求」也是一個很好的結果!
  • 「對不起,我不懂你的問題」也很好!
  • 「我儘力了,但看起來你們沒有我的解決方案應該解決的問題」,這是一個可悲的結果,但它確實存在。
  • 「所有的反饋不會接受,除非它是在C++標準委員會的會議上以書面形式提交」。這時我在過去幾天里看到的答覆,我認為這樣的回復很沒有成效。同樣,用「它是Boost的最流行的庫」這樣的借口為一個庫的設計辯護,就會錯過C++世界的一部分人的意見,他們並不認Boost庫是一個很好的設計。

然而,「遊戲產業」在很大程度上也存在一定的缺陷。遊戲技術一直採用C或C++,因為直到最近都沒有其他可替代的系統編程語言可選擇(現在你至少有Rust作為一個可能的競爭者)。對於依賴C/C++技術的行業來說,這個行業和從業者肯定沒有做足工作,讓我們能夠看到C/C++語言、庫和生態系統的改進。

在互聯網上抱怨容易,但改進C/C++是是一項艱苦的工作。終結C++並不能解決迫在眉睫的問題;他們正在長線規劃。有些公司可以負擔得起;像擁有大型Engine的公司或擁有大型技術團隊的公司都可以做到。我不知道這樣做是否值得。但是如果僅僅說「C++是我們不需要的傻瓜語言」,而從不告訴那些使用它的人我們到底想要什麼,那就有點言不由衷了。

在我的印象,許多從事遊戲開發的人對最近(C++ 11/14/17)添加進去的很多C++特性都覺得非常好。如lambdas, constexpr if 等等。他們傾向於忽略添加到標準庫中的任何東西,這是因為STL的設計/實現存在上面指出的問題(編譯時間長,Debug build性能差),或者對他們沒那麼有用,或者他們的公司在幾年前就已經編寫了自己的容器/字元串/演算法……,所以為什麼要改變已經有效的東西呢?


原文: http://aras-p.info/blog/2018/12/28/Modern-C-Lamentations/

本文為 CSDN 翻譯,如需轉載,請註明來源出處。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 CSDN 的精彩文章:

Python 2 即將停止支持!
全國41611個景點,程序員用Python告訴你哪些地方最值得一游!

TAG:CSDN |