NoS*_*tAl 4 c++ designated-initializer c++20
C++ 有一个不错的新特性:
struct Point{
int x;
int y;
int z;
};
Point p{.x=47, .y=1701, .z=0};
Run Code Online (Sandbox Code Playgroud)
但是如果我添加一个构造函数,那么我将被禁止使用漂亮的指定初始化器语法:
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};
Run Code Online (Sandbox Code Playgroud)
错误:指定的初始值设定项不能与非聚合类型“Point”一起使用
我是否遗漏了一些明显的东西(为什么如果指定的初始化器与具有公共成员但不是聚合的结构/类一起工作会很糟糕)或者这只是一个没有添加到标准中的缺失功能?
指定初始化器是从 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;
};
Run Code Online (Sandbox Code Playgroud)
那么你可以这样做:
Point pt( {.x=3} );
Run Code Online (Sandbox Code Playgroud)
通过访问 的RawPoint指定初始化程序功能。
这与在函数调用中指定初始值设定项的方式相同。
这也有效:
struct Point:RawPoint {
Point( int x, int y, int z = 0 ):
RawPoint{x,y,z}
{}
explicit Point( RawPoint pt ):
RawPoint( pt )
{}
};
Run Code Online (Sandbox Code Playgroud)
聚合初始化(包括使用设计好的初始化器进行初始化)绕过了类的构造函数。
这对于聚合来说不是问题,因为它们不允许具有用户定义的构造函数。但是,如果您允许对具有用户提供的构造函数(做一些有用的事情)的类进行这种初始化,则可能是有害的。
考虑这个例子:
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);
}
};
Run Code Online (Sandbox Code Playgroud)
如果你能做到A{.dummy = 42};,你会在析构函数中得到 UB,并且无法防止这种用法。