大括号初始化允许创建临时的 *private* 结构

mar*_*rkv 6 c++ initializer-list c++11

我刚刚阅读了 Raymond Chen 出色的“旧新事物”中的以下文章: https://devblogs.microsoft.com/oldnewthing/20210719-00/ ?p=105454

我对此有一个疑问,最好在以下代码片段中进行描述。为什么'x3'的初始化是被允许的?我没有看到下面“x2”和“x3”的初始化之间有任何语义差异。

#include <memory>

class X
{
    struct PrivateTag {};   // default constructor is *NOT* explicit
public:
    X(PrivateTag) {}
    static auto Create() -> std::unique_ptr<X> 
    {
        return std::make_unique<X>(PrivateTag{});
    }
};

int main()
{
    auto x1 = X::Create();          // ok:    proper way to create this
  //auto x2 = X(X::PrivateTag{});   // error: cannot access private struct
    auto x3 = X({});                // ok:    why ?!
}
Run Code Online (Sandbox Code Playgroud)

use*_*522 8

可访问性仅限制可以显式写出的名称。它绝不会阻止类型的使用。

decltype例如,始终可以在返回私有类型的函数上使用,然后为其指定一个新名称并通过该名称完全访问它,或者使用模板参数推导为不可访问的类型提供新的别名,通过该别名可以访问它们。

您能做的最好的事情就是通过使用链接博客文章中讨论的方法来使用户很难意外地误用构造函数,但是如果用户有足够的决心,他们几乎总是可以获得完全可访问的别名并PrivateTag使用无论如何,构造函数。

auto x3 = X({}); 没有拼写出成员类名称,因此它的可访问性并不重要。

auto x2 = X(X::PrivateTag{});确实拼写了成员类的名称,因此需要考虑可访问性,这使得它的格式不正确,因为它PrivateTag是私有的并且在命名的上下文中不可访问。


下面是一个规避私有标签技巧的例子,无论是否进行了博文中建议的调整:

struct any_convert {
    template<typename T>
    operator T() const {
        // Here `T` becomes an alias for `PrivateTag`
        // but is completely accessible.
        return T{};
    }
};

//...

auto x4 = X(any_convert{});
Run Code Online (Sandbox Code Playgroud)