为什么我不能在非聚合结构体中使用指定初始化器?

11

C++有一个非常好的新特性:

struct Point{
int x;
int y;
int z; 
};

Point p{.x=47, .y=1701, .z=0};

但是如果我添加了一个构造函数,那么我就无法使用方便的指定初始化语法:

struct Point{
Point(int x, int y, int z = 0): x(x), y(y), z(z){}
int x;
int y;
int z; 
};

static Point p{.x=47, .y=1701, .z = 0};

错误:不能在非聚合类型“Point”中使用指定初始化程序。
我是否忽略了一些明显的东西(为什么将指定初始化程序用于具有公共成员但不是聚合类型的结构体/类会很糟糕),还是这只是一项未被添加到标准中的缺失功能?

1
请确保在所有的C++问题中添加[tag:c++]标签。 - cigien
2个回答

9

聚合初始化(包括使用设计的初始化器进行初始化)绕过了类的构造函数。

对于聚合类型来说,这并不是一个问题,因为它们不允许有用户定义的构造函数。但是,如果你允许这种初始化方式用于具有用户提供的构造函数(执行某些有用的操作)的类中,可能会有害。

考虑以下示例:

class A
{
    static std::map<A *, int> &Indices()
    {
        static std::map<A *, int> ret;
        return ret;
    }

  public:
    int dummy = 0;

    A(int index)
    {
        Indices().emplace(this, index);
    }

    A(const A &) = delete;
    A &operator=(const A &) = delete;
    
    ~A()
    {
        auto it = Indices().find(this);
        std::cout << "Deleting #" << it->second << '\n';
        Indices().erase(it);
    }
};

如果你能执行 A{.dummy = 42};,那么在析构函数中会产生未定义行为,而且没有办法保护免受此类使用的影响。


3
因此,仅限于聚合物是其特点。;) - 463035818_is_not_a_number
啊,这将防止结构体设计者依赖于仅在构造函数运行后使用结构体...有道理。 - NoSenseEtAl

6

指定初始化器是从C中提取的功能,大多数C++编译器也是C编译器,并且它首先是C的一个特性。他们增加了一个限制(即必须按顺序初始化),并将其应用于与C类型匹配的C++类型,然后将其引入到C++中。大多数主要的C++编译器已经将其作为C++扩展实现(没有限制)。该限制被认为是合理的,而使此功能成为可能的“成本”非常低。

一旦你有了构造函数,它就变成了更大的语言问题。初始化程序是否涉及构造函数参数?如果是,则会遇到参数名称不唯一的问题。如果不是,那么当构造函数设置一个值而初始化器设置不同的值时,我们该如何处理呢?

基本上,我们需要通过名称来传递函数参数,以便在构造函数具有指定初始化器的情况下得到明智的结果。这是一个新功能,而不是简单地从C中提取的功能。

对于命名参数的解决方法是:

struct RawPoint{
  int x = 0;
  int y = 0;
  int z = 0;
};

struct Point {
 Point( int x_, int y_, int z_ = 0 ):
   x(x_), y(y_), z(z_)
 {}
 explicit Point( RawPoint pt ):
   Point( pt.x, pt.y, pt.z )
 {}
  int x, y, z;
};

接下来你可以这样做:

Point pt( {.x=3} );

通过访问RawPoint的指定初始化特性来实现。这与在函数调用中具有指定初始化器的方式相同。这也可以起作用:
struct Point:RawPoint {
 Point( int x, int y, int z = 0 ):
   RawPoint{x,y,z}
 {}
 explicit Point( RawPoint pt ):
   RawPoint( pt )
 {}
};

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