【问题标题】:Initializing structs in C++在 C++ 中初始化结构
【发布时间】:2010-05-14 22:45:49
【问题描述】:

作为this question 的附录,这里发生了什么:

#include <string>
using namespace std;

struct A {
    string s;
};

int main() {
    A a = {0};
}

显然,您不能将 std::string 设置为零。有人可以提供关于这里实际应该发生的事情的解释(请参考 C++ 标准)吗?然后举例说明):

int main() {
    A a = {42};
}

这些中的任何一个都有明确的定义吗?

对我来说又是一个尴尬的问题 - 我总是给我的结构构造函数,所以这个问题以前从未出现过。

【问题讨论】:

  • boost::array 类模板也是一个聚合。所以你可以用它来做array&lt;std::string, 2&gt; a = { "foo", "bar" };,例如。另外,我的惰性构造数组也是一个聚合:stackoverflow.com/questions/2662417/…
  • 隐式转换+聚合...ಠ_ಠ
  • @litb 当我第一次看到boost::array 的那个功能时,我有了一个启示,也就是大脑的性满足。很有意义的简单事情往往对我这样做。

标签: c++ struct


【解决方案1】:

你的结构是一个聚合,所以聚合初始化的普通规则适用于它。该过程在 8.5.1 中描述。基本上整个 8.5.1 都是专门用于它的,所以我看不出这里复制整个东西的原因。一般的想法与 C 中的几乎相同,只是适用于 C++:从右侧获取一个初始化器,从左侧获取一个成员,然后使用该初始化器初始化该成员。根据 8.5/12,这应该是一个复制初始化

当你这样做时

A a = { 0 };

您基本上是用0 复制初始化a.s,即对于a.s,它在语义上等价于

string s = 0;

上面的编译是因为std::string 可以从const char * 指针转换。 (这是未定义的行为,因为在这种情况下空指针不是有效参数。)

您的42 版本无法编译,原因与

string s = 42;

不会编译。 42 不是空指针常量,std::string 没有办法从int 类型转换。

P.S. 以防万一:请注意,C++ 中 aggregate 的定义不是递归的(例如,与 POD 的定义相反)。 std::string 不是聚合,但它不会改变您的 AA 仍然是一个聚合。

【讨论】:

  • §12.6.1 也是相关的,如 § 8.5.1 13 所述。
  • @outis:我浏览了 12.6.1,但我无法立即看到它在 8.5 中添加的内容。每次 12.6.1 处理聚合初始化时,似乎都回溯到 8.5 :)
  • 有趣的是,basic_string(size_type n, charT c, const Allocator a=Allocator())size_type n 没有默认值是有原因的。原因是在指针和整数上重载是个坏主意。值 0(零)严格来说是一个整数,而不是指针,因此您将无法使用空指针通过指针重载进行构造,除非您当然要显式转换。如果您在字符串构造时指定字符串长度,该标准通过要求字符类型来避免这种混淆。
  • 它指定聚合的类成员何时被复制初始化、值初始化或初始化器格式错误。
【解决方案2】:

8.5.1/12 “聚合”说:

使用初始化器列表中的初始化器初始化聚合成员时,会考虑所有隐式类型转换(第 4 条)。

所以

A a = {0};

将被初始化为 NULL char*(如 AndreyTJohannes 所示),并且

A a = {42};

将在编译时失败,因为没有与 std::string 构造函数匹配的隐式转换。

【讨论】:

    【解决方案3】:

    0 是一个空指针常量

    S.4.9:

    空指针常量是整数类型的整数常量表达式 (5.19) 右值,其计算结果为 零。

    空指针常量可以转换为任何其他指针类型:

    S.4.9:

    空指针常量可以转换为指针类型;结果是那个的空指针值 输入

    你对A的定义被认为是一个聚合:

    S.8.5.1:

    聚合是没有用户声明的构造函数、没有私有或受保护的数组或类 非静态数据成员,没有基类,也没有虚函数。

    您正在指定一个初始化子句:

    S.8.5.1:

    当一个聚合被初始化时,初始化器可以包含一个初始化器子句,该子句由一个大括号括起来, 聚合成员的初始化子句的逗号分隔列表

    A 包含std::string 类型聚合的成员,初始化子句适用于它。

    您的聚合已复制初始化

    当一个聚合(无论是类还是数组)包含类类型的成员并由括起来的大括号初始化时 初始化器列表,每个这样的成员都被复制初始化。

    复制初始化意味着你有std::string s = 0std::string s = 42的等价物;

    S.8.5-12

    在参数传递、函数返回、抛出异常(15.1)、处理中发生的初始化 一个例外(15.3),大括号括起来的初始化列表(8.5.1)被称为复制初始化并且是等价的 形式为 T x = a;

    std::string s = 42 不会编译,因为没有隐式转换,std::string s = 0 会编译(因为存在隐式转换)但会导致未定义的行为。

    std::stringconst char* 构造函数未定义为 explicit,这意味着您可以这样做:std::string s = 0

    只是为了表明事情实际上正在被复制初始化,你可以做这个简单的测试:

    class mystring
    {
    public:
    
      explicit mystring(const char* p){}
    };
    
    struct A {
      mystring s;
    };
    
    
    int main()
    {
        //Won't compile because no implicit conversion exists from const char*
        //But simply take off explicit above and everything compiles fine.
        A a = {0};
        return 0;
    }
    

    【讨论】:

      【解决方案4】:

      正如人们所指出的,这“有效”是因为 string 有一个可以将 0 作为参数的构造函数。如果我们说:

      #include <map>
      using namespace std;
      
      struct A {
          map <int,int> m;
      };
      
      int main() {
          A a = {0};
      }
      

      然后我们得到一个编译错误,因为地图类没有这样的构造函数。

      【讨论】:

      • 为什么你的头像没有出现在这个答案中?它是否隐藏在社区答案中?
      • @Johannes 一个谜!您想将其报告为 meta 上的错误还是我应该报告?
      • 你的身份不属于你,它属于集体。
      【解决方案5】:

      在 21.3.1/9 中,该标准禁止 std::basic_string 的相关构造函数的 char* 参数为空指针。这应该会抛出std::logic_error,但我还没有看到标准中的哪个地方保证违反先决条件会抛出std::logic_error

      【讨论】:

      • 如果我没记错的话,违反先决条件保证了未定义的行为,而不是异常。
      • @James g++ 4.0.1 on OS X 10.5.8 在构建时抛出std::logic_error。 19.1.1 说这就是 logic_error 的用途,但我无法保证当违反不变量或前提条件时会发生这种情况。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-16
      • 1970-01-01
      相关资源
      最近更新 更多