C++11中的首选初始化方式

13
int i = 0; // (a) Old C style should I use it?
int i{0}; // (b) Brace direct init
int i{}; // (c) Same as (b)
int i = {0}; // (d) as (b)
int i = {}; // (e) as (c)
auto i = 0; // (f) auto = int in this case.
auto i = int{0}; // (g) auto = more specific.
auto i = int{}; // (h) same as above (g)

该使用哪个? Sutter建议使用:

int i = 0;
auto i = 0;

为什么不:

int i = {0};
auto i = int{0};

在某些情况下,我应该去掉“=”符号吗:

int i{0};
auto i{0}; // i is not what some might expect in this case. So I would prefer using "=" everywhere possible like int i = {0}; ...

编辑: 这似乎是我最希望的,也最为一致的:

rectangle       w   = { origin(), extents() }; 
complex<double> c   = { 2.71828, 3.14159 }; 
mystruct        m   = { 1, 2 }; 
int             a[] = { 1, 2, 3, 4 };
vector<int>     v   = { 1, 2, 3, 4 };
point           p   = {}; // Default initializes members
int             i   = {0}; // Checked assembly for this and it's binary the same as int i{0}; could be written also as int i = {};
string          s   = {""}; // Same as string s = {}; (OR) string s;

现实生活中的例子:

std::string       title              = { pt.get<std::string>("document.window.title") };
const std::string file               = { R"(CoreSettings.xml)" };
int_least64_t     currentTick        = { 0 }; // (OR) int_least64_t currentTick = {};
bool              isRunning          = { false }; // (OR) bool isRunning = {};
App*              app                = { nullptr }; // (OR) App* app = {};
Event             event              = {};
double            detectedFrameRate  = { 1000000000.0 / (swapIntervalDeltaCumulative / 20.0) };
double            precision          = { static_cast<double>(boost::chrono::high_resolution_clock::period::num)
                                           / boost::chrono::high_resolution_clock::period::den };
auto              timeSpan           = boost::chrono::duration_cast<boost::chrono::nanoseconds>(nowTime - startTime);

另一种选择是:

std::string       title             { pt.get<std::string>("document.window.title") };
const std::string file              { R"(CoreSettings.xml)" };
int_least64_t     currentTick       { 0 }; // (OR) int_least64_t currentTick{};
bool              isRunning         { false }; // (OR) bool isRunning{};
App*              app               { nullptr }; // (OR) App* app{};
Event             event             {};
double            detectedFrameRate { 1000000000.0 / (swapIntervalDeltaCumulative / 20.0) };
double            precision         { static_cast<double>(boost::chrono::high_resolution_clock::period::num)
                                        / boost::chrono::high_resolution_clock::period::den };
auto              timeSpan          = boost::chrono::duration_cast<boost::chrono::nanoseconds>(nowTime - startTime);

如果不使用大括号,代码将难以阅读或容易出错。
int_least64_t     currentTick        = 0; // C style - changed this from double to int recently and compiler did not complain so I had something like int_least64_t currentTick = 0.0; ugly!
bool              isRunning          = false; // C style
App*              app                = nullptr; // C mixed with C++11 style;
Event             event; // might not be initialized by all compilers
int               someInt            = func(); // func() returns double no error but narrowing.

7
我会尽可能地用最贴切、最简短、最不含糊的中文来翻译。个人认为最好的方式是使用 int i = 0;。因为 auto i{0}; 定义的是一个 std::initializer_list<int>,而不是一个整数类型的变量。 - Deduplicator
1
对于 intsi = 0i = {0} 是相同的。只有当你开始处理其他类型时,这种区别才变得更加相关。我建议不要使用 auto i{0} 来表示一个 int,因为现在读者需要检查 i 是否被定义为一个类。相反,通过使用 auto i = 0,他们知道他们只是处理一个整数字面量。 - shuttle87
1
你也可以这样做:int i = (2 * (5 + 27) * 91 * 2) % 4 - SomeWittyUsername
1
这个问题太宽泛了...基本上是一项调查。目前还没有明确的答案。 - Puppy
1
如果你在寻求一致性,那么恐怕你会失望。有时候你必须使用大括号初始化(例如 vector<int> v{1,2,3,4}; 来初始化元素值),而有时候你则不能这样做(例如 vector<int> v(2,42); 来选择另一个构造函数)。总的来说,尽量让代码尽可能易读,这并不意味着要对其施加奇怪的虚假一致性。 - Mike Seymour
显示剩余2条评论
6个回答

10

就像你的例子中简单的int一样,我同意

int i=0; 

使用花括号初始化(在程序员中)可能是最常见的,但我认为使用它有优势,使其更可取。例如

int i = 3.99;    // i gets 3; no warning, no error
int i{3.99};     // i gets 3; warning: "narrowing conversion"

在我看来,这是一个更好的编写无 bug 代码的方法。

但和 auto 搭配使用则更加危险。我通常只在以下情况下使用 auto

  • 范围 for 循环中的临时变量 (例如: for (const auto &n : mycollection))
  • 用于简化命名 lambda 的声明
  • 当显式使用迭代器实例(而不是范围 for)时,用于迭代器示例
  • 在模板代码中,这样做可以避免创建冗长的 typedef

4
欢迎来到初始化列表/统一初始化地狱。 - Puppy
但是你会使用 int i = {3}; 吗?只是为了与 auto i = int{0}; 保持一致。对我来说,在任何地方都使用 = 进行初始化更易读。 - CrHasher
不,我永远不会使用int i={3},因为它的缺点很明显。可以使用int i{3},或者如果其他人阅读你的代码并不是C++11专家,则可以使用int i=3,因为后者的语法早在Algol60之前就已经存在了。 - Edward
@Edward:为什么你要避免使用int i={3};,它为什么是两全其美的最糟糕的选择?int i = 0 是缩小的复制初始化,而 int i = {3} 是没有缩小的复制列表初始化,大多数编译器会将其优化为 int i{0} 直接初始化。这样,"=" 等号只是为了可读性,{} 大括号可以防止缩小,同时看起来更像 int i = 0; 但带有大括号。 - CrHasher
@CrHasher:请看我对Deduplicator答案的评论。 - Edward
@Edward:我点赞你的回答,因为在我看来它是最相关的。我会选择统一初始化语法 int i{0}; - CrHasher

5

有一些错误的推导:

auto i{0}; // [comment omitted]
int i();

第一个定义了istd::initializer_list<int>

第二个声明了一个名为i的外部函数,返回int类型且不带参数。

经验法则:

  • Use auto where it saves typing and the type or its behavior is obvious. Examples:

    auto x = new mymegathingy;
    auto y = container.begin();
    auto z = filestream.seekoff(0, basic_ios::curr);
    
  • Use assignment where that works (A potential temporary will be optimized away by any current compiler, possible when lhs and rhs have different types).

    int i = 0;
    int* i = 0; // For many types passing `nullptr` is better.
    
  • Use universal initializer syntax where assignment does not work.

    std::vector<int> i = {1,2,3};
    auto i = new int[]{1,2,3};
    
  • You might want to use direct constructor call where at least one obviously non-type argument is given, to avoid curly braces:

    int i(0);
    

注意,使用通用初始化语法进行初始化与auto的混合使用会导致我们得到一个 std::initializer_list<>

auto i{0};

避免在不传递至少一个明显的非类型参数的情况下使用旧式init,否则您可能会意外地声明一个函数:

int i();

“使用能用赋值的地方”这个建议可能需要澄清。如果我们有MyClass frank = 0,我们可能会(如果编译器没有进行太多优化)创建一个临时的MyClass实例,然后进行一次移动。而使用更直接的MyClass frank{0}可以避免这种情况。 - Edward
我更喜欢使用 int i = {0}; 和 std::vector<int> v = {1,2,3};,因为如果你将它们写在下面,看起来更加一致。 - CrHasher
我会在这两个语句中省略掉 = - Edward
1
请注意,委员会已经在考虑更改auto i{0};以推断int(并完全禁止auto i{1, 2};)。 - T.C.

1
对于类型为int的变量,i = 0i = {0}是相同的。int i = 0最易读懂,因为人们习惯这样写。
如果你选择使用自动类型推导,你需要知道auto i{0}auto i = 0实际上定义了不同的类型。(请参考Deduplicator的评论)
看一下这段代码:
#include <iostream>
#include <typeinfo>

int main(){
    auto a = 0;
    std::cout << "a is of type:" << typeid(a).name() << std::endl;

    auto b = int{0};
    std::cout << "b is of type:" << typeid(b).name() << std::endl;

    auto c{0};
    std::cout << "c is of type:" << typeid(c).name() << std::endl;
}

当我们运行这个程序时,会得到以下结果:
a is of type:i
b is of type:i
c is of type:St16initializer_listIiE

“auto c {0}”实际上创建了一个“std::initializer_list”,这几乎肯定不是问题发布者所期望的。 基本上,当涉及到可读性时,这里有一堆潜在的严重问题。 下面是我使用“g++ -Wall -std=c++11 main.cpp”(g++版本4.7.2)编译的内容。
#include <iostream>
#include <typeinfo>
#include <vector>

class d{
    public:
        std::vector<int> v;
        d(std::initializer_list<int> l) : v(l) {
             std::cout << "constructed class d with a " << l.size() << "-element list\n";
        }
};

int main(){
    auto d{0};
    std::cout << "d is of type:" << typeid(d).name() << std::endl;
}

当我们运行这个程序时,会得到以下结果:
d is of type:St16initializer_listIiE

这可能也不是您预期的结果。显然,如果您正在编写生产代码,则希望选择更好的类名,但我很惊讶编译时没有出现警告。

当你说 int i = 0; 和 int i = {0}; 是一样的时候,你是错的。考虑这种情况 int i = {0.0},它会导致编译器错误。更好的做法是 int i = { func() }; 如果 func 返回 double,编译器会报错,所以这样更安全,不是吗? - CrHasher
1
请纠正我如果我错了,但是int i = {0}int i = {0.0} 明显是两个不同的东西。 - Puppy
我所说的就是我写的那样。请注意,int i = {0} != int i{0.0}。此外,int i = {func()} 是完全不同的情况。 - shuttle87
@shuttle87:只是想知道我们是否对于为什么在某些情况下 int i = {0}; 比 int i = 0; 更好的理解是一致的。 - CrHasher

0

我同意Deduplicator的观点。 我会使用:

int i = 0;

这是编程中最简单、最小和最为人所知的方式。


1
所以 free,使用起来是疯狂的。 - Puppy
2
同意去重器的观点。保持简单。在确实需要初始化列表和auto并且有用的地方使用它们。在那之前,保持简单。 - Jens Munk

0

我的建议是:

1)仅在模板构造中使用auto,您可以从中受益,并且对于迭代器,其中类型名称可能很长(如果您喜欢)。不要将其用于原始类型,尤其不是引用。如果函数返回引用并且您想使用auto,则需要使用auto&。如果没有进行复制,这可能非常棘手。

2)将代码视为可以在纸张上阅读并明确表达的内容。如果类型固定,请勿尝试隐藏类型。

3)初始化列表可能很危险。在需要时使用它们。我见过许多结构使用初始化列表进行初始化,然后开发人员添加了另一个成员。某些编译器不会发出警告(糟糕)。


如果您不能轻松地从周围的代码中推断出auto的含义,我会说这是代码的问题,而不是auto的问题。典型的例子:当然,在一个有100行的函数中使用auto并不会使理解正在发生的事情变得更容易。但是核心问题显然不在于使用auto。此外,请给我们提供一个可靠的来源,证明只有新手才会在调试器中使用鼠标悬停:P - stijn
2
Bjarne Stroustrup使用Sam作为他的代码编辑器。http://en.wikipedia.org/wiki/Sam_(text_editor)#Endorsers Sam不支持使用鼠标悬停来提供类型信息。他还说,在他使用IDE的不寻常情况下,他希望“能够仅从源文件中理解我的系统。”http://msdn.microsoft.com/en-us/magazine/cc500572.aspx相比Bjarne Stroustrup,其他人都是菜鸟。因此,只有菜鸟使用鼠标悬停。QED。 - Ross Ridge
2
谢谢Ross。我完全同意。我使用Emacs作为我的代码编辑器,不到5%的时间使用IDE,不到1%的时间使用调试器。 - Jens Munk

-2

请专门使用auto name = initializer;。有几个地方需要使用auto&&auto name = (Base*)initializer;。绝对不要使用花括号,因为统一初始化非常糟糕。


如果你的回答仅适用于使用auto的情况,提及这一点会很有帮助。如果不是,请详细说明为什么使用显式类型时,你会说“统一初始化非常糟糕”。 - Edward
我认为我们几乎总是应该使用auto。我不同意我们永远不应该使用通用初始化列表的说法。特别是,至少在构造函数的初始化列表中这样做很难找到缺点。此外,请使用C++风格的转换。 - Mark
初始化列表和统一初始化是两个完全独立的特性,它们相互交互时出现的问题是避免使用统一初始化的主要原因。 - Puppy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接