【问题标题】:Constructors for structs that don't do anything不做任何事情的结构的构造函数
【发布时间】:2012-07-18 08:37:14
【问题描述】:

编辑:为了清楚起见,结构没有做任何事情,因为它没有任何功能。我想我给人的印象是我认为使用初始化列表并将构造函数的主体留空是手头的问题。

假设我正在使用一个结构来保存两个值,并且我有一个构造函数,所以我可以创建一个像这样的任意结构:

struct twoValues
{
    int x;
    int y;

    twoValues(int x_, int y_):y(y_),x(x_)
    {}
};

someFunction(twoValues(1,2));

这让我不必这样做:

twoValues anInstance;
anInstance.x=1;
anInstance.y=2;
someFunction(anInstance);

编辑:你说得对,我也可以使用以下内容进行初始化:

twoValues anInstance = {1,2};

我认为这没有任何问题,但我从 C++ 测试中得到了一些反馈,其中一个负面反馈标记是“不做任何事情的结构的构造函数”。我和那个测试我的人接触有限,所以从来没有问过为什么。

这是一件坏事,为什么?我宁愿继续这样做。

【问题讨论】:

  • 没什么问题。

标签: c++ constructor struct


【解决方案1】:

这取决于结构的用途。正如其他人所说, 构造函数意味着该类不再是 POD,并且 聚合初始化不能用于它。特别是,您 在命名空间范围内不能有类似的东西:

TwoValues const table[] =
{
    { 1, 2 },
    { 3, 4 },
    // ...
};

你可以拥有:

TwoValues const table[] =
{
    TwoValues(  1, 2  ),
    TwoValues(  3, 4  ),
    // ...
};

但它更冗长,它意味着动态初始化,这可能 导致初始化问题的顺序。

另一方面,没有构造函数,你不能创建临时的 即时实例。而不是:

extern void f( TwoValues const& );
//  ...

f( TwoValues( 1, 2 ) );

你必须写:

extern void f( TwoValues const& );
//  ...

TwoValues tmp = { 1, 2 };
f( tmp );

如果对象是动态分配的,那就更糟了,因为你 要么必须先分配,然后初始化,要么创建一个临时的 上面,然后写new TwoValues( tmp )并使用隐式复制 构造函数。

你必须选择。根据结构的用途,一个或 另一个将是首选;一方面,我有很多结构 仅在静态表中使用,并且故意没有 构造函数(并且只包含支持静态的类型 初始化),并经常使用它们来配置代码。在另一 手,我也有很多类内部的结构,沿着 树或图中Node 的行;这些几乎总是有 构造函数,以方便动态创建它们。没有 在不知道结构在您的角色中的作用的情况下“正确”回答 应用。

【讨论】:

  • 如果你打算这样写答案,你应该提到 C++11 中的新特性,否则几个月后这篇文章就会变得乱七八糟(特别是因为你没有说明什么正在编写适用于 C++03,在这种情况下我们需要明确)
  • 我在您的帖子中添加了一个标题,说明它仅与 C++03 相关
  • @refp 我删除了您添加的误导性标题。我不确定你说的是哪一点:C++11 确实添加了一些额外的初始化语法,但是关于动态和静态初始化的区别并没有改变,大多数程序员至少需要 5 年才能使用任何的新语法。
  • 你必须写TwoValues tmp = {1,2}; f (tmp);(除其他外),这在 C++11 中并非如此
  • C++11 没有放宽规则以使构造函数不会取消 struct 作为 POD 的资格吗?
【解决方案2】:

声明一个空的构造函数有副作用..

即使你的构造函数有一个空的body,它仍然被认为是一个构造函数,因此某些对象属性会丢失——比如对象是POD。

某些情况需要使用 Plain Old Data 类型,这可能会导致在没有特定原因的情况下执行您所做的事情是不可取的。

在下一节中阅读有关没有默认构造函数的初始化的更多信息。


&&

您可以使用值初始化您的成员,而无需显式定义“不做任何事情的构造函数”,只需使用如下 sn-p 中的大括号初始化语法。

struct Obj {
  int x, y;
};

Obj a = {1,3}; /* a.x = 1, a.y = 2 */

没有构造函数的缺点是在编写C++03时,在某些情况下无法使用 = { ... }初始化对象。

C++11 为您解决了这个问题,相关示例请参见下一节。


在 C++11 中,使用大括号 ( = { ... }) 的初始化被赋予了更多的功能。

如下面的 sn-p 所示,即使我们使用与本文前面相同的初始化形式,也会调用 Obj 的定义构造函数。

struct DoubleBoth {
  DoubleBoth (int x, int y)
    : x(x*2), y(y*2) 
  {}  

  int x, y;
};

下面的 sn-ps 在 C++11 之前都是非法的:

DoubleBoth a = {1,2}; /* a.x = 2, a.y = 4 */

struct Wrapper {
  Wrapper ()
    : value {3,4}
  {}

  DoubleBoth value;
};

void func (DoubleBoth v = {1,2}) {  // c++11 only
  ...
}

func ({4,5}); // c++11 only, c++03 requires `DoubleBoth obj (0,1); func (obj);`

【讨论】:

    【解决方案3】:

    使用这种构造函数比使用列表初始化要安全得多。

    当你用列表初始化时:

    twoValues anInstance = {1, 2};
    

    这实际上取决于结构中成员的顺序。在大多数情况下,一开始是完全随机的。

    如果另一个程序员碰巧添加了另一个成员,或者按字母对成员进行排序,它将无法正常工作。

    拥有一个通过 NAME 将值分配给正确成员的构造函数要安全得多。

    【讨论】:

    • 好点。开发人员知道弄乱函数或构造函数中的参数顺序是很敏感的。但是结构中成员的顺序并没有那么多,这就是为什么依赖该顺序是危险的,尤其是在 C++ 中
    • 成员的顺序不应该是“完全随机的”,除非类作者在工作中喝酒,并且应该枪杀按字母顺序对成员进行排序的开发人员,特别是如果他们对布局是一部分的结构这样做界面。
    • 对。但是很难找到人来射击他们,所以就发生了这样的事情。
    【解决方案4】:

    您已阻止 twoValues 成为 POD 并阻止结构的实例被默认或值初始化,这通常是理想的属性。就个人而言,如果您需要一种简单的 C++03 友好方法,我更希望有一个免费的函数来制作临时实例。

    例如

    twoValues makeTwoValues(int x_, int y_)
    {
        twoValues tmp = { x_, y_ };
        return tmp;
    }
    
    void f() {
        someFunction(makeTwoValues(1,2));
    }
    

    例如初始化twoValues类型的成员

    class X {
        twoValues tv;
    public:
        X(int x, int y) : tv(makeTwoValues(x, y)) {}
    };
    

    【讨论】:

    • “这通常是理想的属性”——您可能想对此进行扩展——特别是,因为它也有明显的缺点(至少在 C++0x 中,请参阅 juan 的答案)
    • @KonradRudolph:我看不出缺点(除了必须写函数),你能扩展吗?
    • 关于它不再能够默认初始化的声明是错误的,构造函数可以有默认参数,所以使用它会使其默认初始化 ,即使已经定义了构造函数。
    • @Charles 只是你必须编写一个函数。不是一个很大的缺点,但毕竟这通常是构造函数为您提供的,free 函数是一种丑陋的解决方法,您正在编写类似构造函数的东西以避免使用构造函数。从概念上讲,这只是不确定的,尽管我知道保留 POD 可能是可取的。
    • @KonradRudolph:我认为这不是一种解决方法。创建一个聚合类型和创建该类型的初始化值的函数是一个积极的选择。如果您希望与 C 兼容,它特别有用,但如果您想维护所有潜在的 POD 优化算法并保留创建廉价未初始化实例的能力,即使在仅 C++ 的环境中也是如此。
    【解决方案5】:

    可能是因为你可以这样初始化它:

    twoValues anInstance = {1, 2};
    

    但是,如果没有构造函数,您将无法在 C++03 中的另一个结构的初始化程序列表中初始化 anInstance。例如:

    struct Bar {
      twoValues v_;
      Bar() : v_(1,2) {} // error!
      Bar() { v_ = {1,2}; } // have to assign value in constructor body
      Bar() : v_{1,2} {} // OK in C++11
    };
    

    所以实际上构造函数做了一些事情,它在 C++03 中确实起到了非常有用的作用。在 C++11 中这不是问题。

    【讨论】:

    • "没有构造函数,你不能在另一个结构的初始化列表中初始化 anInstance"。实际上他可以,但为此他需要编写一些额外的代码(在 C++03 中)。而在 C++11 中,他可以写成 Bar() : v_{1,2} {} 。看到这个:ideone.com/Pn1rA
    【解决方案6】:

    这个构造器没有问题。

    此外,它做了一些事情,它影响 x 和 y 的值,这是我对这样一个构造函数的期望。

    但是,也许您的公司中有一些编码规则规定您应该以另一种方式进行编码。在这种情况下,我建议您询问给您反馈的人并进行必要的修改。

    【讨论】:

      【解决方案7】:

      构造函数使结构成为非 POD,这在某些情况下可能是不可取的。

      如果你的 struct 没有构造函数,那么你的 struct 将是 POD1,你可以这样初始化:

      twoValues anInstance = {1,2}; //C++03 and C++11 both!
      twoValues anInstance {1,2};   //C++11 only
      

      哪个好。

      1.实际上还有其他一些东西使结构非 POD。因此,仅仅因为您的结构没有构造函数并不一定意味着它是 POD。您想查看this post 以了解其他内容。

      【讨论】:

      • 而且它使结构不可默认构造,这就像一个坏主意。
      • @rodrigo 不,不一定。事实上,除非明确要求,否则你应该不使类默认可构造。
      • @KonradRudolph:同意,但它不是一个类,它是一个具有所有公共成员变量的结构(没有不变量:最可能的用途只是数据聚合)。对于这样的类型,我认为不能默认构造几乎没有什么好处。
      • 初始化(至少在 C++11 中)依赖于聚合的类型,不一定是 POD。
      • @juanchopanza:嗯,在 C++11 中,如果其构造函数采用 std::initializer_list,则该类型甚至不需要聚合。 (至于你的困惑,POD是一种聚合,struct可以是POD)。
      【解决方案8】:

      结构可以有构造函数,语法与 C++ 中的类相同。类和结构之间的区别在于,类成员默认是私有的,而结构成员默认是公共的,但是联合不允许在结构中使用构造函数。不过,您可以在联合上创建一个构造函数。

      【讨论】:

        猜你喜欢
        • 2017-06-13
        • 2012-01-13
        • 2010-11-04
        • 1970-01-01
        • 1970-01-01
        • 2010-10-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多