【问题标题】:How do you initialize a map which takes a struct as value?你如何初始化一个以结构为值的映射?
【发布时间】:2011-05-22 17:54:24
【问题描述】:

我使用映射作为 ID 的关联数组 -> 值,其中值是定义对象的结构:

#include <map>

struct category {
        int id;
        std::string name;
};

std::map<int, category> categories;

int main() {
        categories[1] = {1, "First category"};
        categories[2] = {2, "Second category"};

}

上面的代码用g++编译,但是有以下警告:

warning: extended initializer lists only available with -std=c++0x or -std=gnu++0x

我在这里阅读了有关结构初始化的各种问题/答案,但我仍然有点困惑。我有一系列相关问题:

  1. 我可以添加编译器选项 -std=c++0x 并完成警告,但仍然对潜在问题一无所知。如果我在类别结构中添加一个方法,事情不会中断吗?

  2. 以更符合 C++03 的方式初始化此 POD 结构(类别)的最佳方法是什么?

  3. 基本上,我还不确定以一种方式而不是另一种方式做事的后果。这种关联数组(其中键是对象的 ID)在 PHP 中很容易,我仍在学习在 C++ 中正确的方法。在上面的代码上下文中我有什么需要注意的吗?

编辑
以下问题是相关的,但我第一次看的时候没看懂答案:
C++ initialize anonymous struct
c++ Initializing a struct with an array as a member
Initializing structs in C++

【问题讨论】:

    标签: c++ map struct initialization associative-array


    【解决方案1】:

    在 C++ (ISO/IEC 14882:2003) 中,括号括起来的表达式列表可用于在定义它的声明中初始化 聚合 类型的变量。

    例如

    struct S { int a; std::string b; };
    
    S x = { 39, "Hello, World\n" };
    

    聚合 类型是一个数组或类,没有用户声明的构造函数,没有私有或受保护的非静态数据成员,没有基类,也没有虚函数。请注意,类 aggregate 不必是 POD 类,并且任何数组都是 aggregate,无论它作为数组的类型是否是聚合。

    但是,用大括号括起来的表达式列表仅作为聚合的初始化器有效,在其他上下文中通常不允许使用,例如赋值或类构造函数的成员初始化列表。

    在下一个 C++ 版本 (C++0x) 的当前草案中,允许在更多上下文中以及当对象被从这样的 initializer list 初始化,它被称为 list-initialization

    允许此类列表的新上下文包括函数调用中的参数、函数返回、构造函数的参数、成员和基初始化器以及赋值的右侧。

    这意味着这在 C++03 中无效。

    int main() {
            categories[1] = {1, "First category"};
            categories[2] = {2, "Second category"};
    }
    

    相反,您可以这样做。

    int main() {
            category tmp1 = { 1, "First category" };
            category tmp2 = { 2, "Second category" };
    
            categories[1] = tmp1;
            categories[2] = tmp2;
    }
    

    或者。

    int main() {
            category tmpinit[] = { { 1, "First category" },
                                   { 2, "Second category" } };
            categories[1] = tmpinit[0];
            categories[2] = tmpinit[1];
    }
    

    或者,您可以考虑为您的类型创建一个工厂函数。 (您可以为您的类型添加一个构造函数,但这会使您的类成为非聚合类,并且会阻止您在其他地方使用聚合初始化。)

    category MakeCategory( int n, const char* s )
    {
        category c = { n, s };
        return c;
    }
    
    int main()
    {
        categories[1] = MakeCategory( 1, "First category" );
        categories[2] = MakeCategory( 2, "Second category" );
    }
    

    【讨论】:

    • 感谢@Charles Bailey 花时间总结整个讨论。感谢@sbi 推动这一进程。我非常感谢这一切。 :)
    • 顺便说一句,问题的标题最终并没有反映讨论的内容。随意将问题的标题编辑为更合适的名称。
    • 天哪,我从来不知道你不需要构造函数来以这种方式初始化某些东西......为什么没有人告诉我这个!?
    【解决方案2】:

    在当前的 C++ 标准中,您可以使用初始化列表来初始化仅包含 POD 值的数组和结构。下一个标准(又名 C++0x 或 C++1x)将允许对包含非 POD 类型的结构执行相同操作,例如标准::字符串。这就是警告的内容。

    我建议您向category 添加一个简单的构造函数,该构造函数接受 id 和名称,然后直接调用该构造函数:

    #include <map>
    #include <string>
    
    struct category {
            category() : id(0), name() {}
            category(int newId, std::string newName)
             : id(newId), name(newName) {}
    
            int id;
            std::string name;
    };
    
    std::map<int, category> categories;
    
    int main() {
            categories[1] = category(1, "First category");
            categories[2] = category(2, "Second category");
    
    }
    

    【讨论】:

    • 这并不奇怪,而是标准行为。您想要存储在 STL 容器中的任何类型都需要默认构造函数。没有任何构造函数的类和结构会自动获得一个,但是一旦你定义了自己的,你也必须定义一个默认的。
    • @Mephane:我会小心重载比较运算符的语义,尤其是相等性。您的代码的用户可能会注意到您定义了它们,而不是阅读实现(或规范)。此外,要查找 id,您需要使用虚拟类别对象。如果您关心一致性,我更喜欢category tmpcat(id, name); categories[tmpcat.id] = tmpcat;
    • 虽然解决方案是正确的,但最初的陈述是错误的。 C++ 继承了 C 的聚合初始化,即使在 C++03 中也可以使用。这个struct X { int i, j; } = {42, 24}; 也应该在 C++03 中工作。问题是 std::string 不是 POD,因此 category 也不是,这就是为什么它不能在 C++03 中使用这种语法初始化的原因。 C++1x 确实会解决这个问题。
    • 您想要存储在标准容器中的任何类型都不需要默认构造函数,这不是真的。某些容器中的一些常见操作需要默认构造函数(std::map 中的op[] 就是一个示例),但大多数操作不需要包含的类类型是默认可构造的。
    • @sbi:在 C++03 中能否用初始化列表初始化一个类型只取决于它是否是一个聚合,而不取决于类型的 POD。在 C++03 中完全可以这样做:struct S { int a; std::string b; } x = { 39, "Hello, World\n" };。在 C++03 中你不能做的是任何形式的 list-initialization,你可以在任何地方使用括号括起来的表达式列表,而不是在聚合类型的对象的声明中。
    【解决方案3】:

    我知道这是旧的,但也可以使用

    std::map<int, std::pair<std::string, int>> categories
    

    或:

    std::map<int, std::tuple<std::string, int, double>> categories
    

    如果需要更多。

    【讨论】:

      【解决方案4】:

      我们使用的初始化类型仅在称为 C++0x 的新兴 C++ 标准中引入,因此有警告和编译器选项。一些编译器,如 g++,已经支持一些新特性,但标准本身还没有被接受。正如我们所知,它为 C++ 添加了许多新特性。您可以在Stroustrup's site 上阅读更多信息。

      要初始化结构,您可以(自然地)添加一个ctor,例如

      struct category {
              category(int i, const std::string& n): id(i), name(n) {}
              int id;
              std::string name;
      };
      

      然后按如下方式初始化地图:

      categories[1] = category(1, "First category");
      

      请注意,从const char* 到字符串的隐式转换将在这里起作用,否则您也可以使用const char* 定义一个ctor。

      【讨论】:

      • 和 Mephane 相隔 1 分钟交叉发布了相同的答案。答案是一样的,但是对于空的构造函数,没有它代码将无法编译。请参阅我在 Mephane 的回答中的评论。无论如何,它确认使用构造函数是可行的方法。谢谢。 (和 +1)
      • @augustin:正确的重新清空 ctor。请注意,我更喜欢传递const std::string&amp; n 的const ref 参数而不是std::string n,以避免临时内存分配和复制。并感谢您的认可:)
      • 关于 const std::string& n 的优点。感谢您的帮助。
      【解决方案5】:

      您需要的功能在 C/C++ 中称为 聚合。通过搜索“aggregate c++”,您会发现很多详细说明原因和方法的信息。

      1- 如果我添加一个 类结构的方法?

      除非该方法影响底层 C++ 内存布局,否则不需要。例如,普通函数无关紧要,但虚函数会,因为它很可能布置在类成员之前。

      2- 最好的方法是什么 初始化此 POD 结构(类别) 以更符合 c99 的方式?

      按照其他响应者的建议使用构造函数。

      3- 有什么我应该支付的吗 关注的范围内 上面的代码?

      它可能涉及冗余副本,具体取决于您设计构造函数的方式。但只有当您经常需要初始化并且您真正关心性能时才重要。

      【讨论】:

      • +1,谢谢。这对我来说是新事物,我现在正在按照您的建议进行研究。
      • 感谢您的建议,我找到了这个有用的页面。聚合初始化:codeguru.com/cpp/tic/tic0077.shtml
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-17
      • 1970-01-01
      • 1970-01-01
      • 2013-12-18
      • 2021-05-03
      相关资源
      最近更新 更多