简体   繁体   English

如何使用带有 std::initializer_list 的构造函数设计类?

[英]How to design classes with constructor taking a std::initializer_list?

When classes have a constructor overload taking a std::initializer_list , this overload will take precedence even if other constructor overloads are seemingly a better match.当类的构造函数重载采用std::initializer_list ,即使其他构造函数重载看起来更匹配,该重载也会优先。 This problem is described in detail in Sutter's GotW#1 , part 2, as well as Meyers' Effective Modern C++ , Item 7.这个问题在 Sutter 的GotW#1 ,第 2 部分以及 Meyers 的Effective Modern C++ ,Item 7 中有详细描述。

The classic example of where this problem manifests itself is when brace-initializing a std::vector :出现此问题的经典示例是在大括号初始化std::vector

std::vector<int> vec{1, 2};
// Is this a vector with elements {1, 2}, or a vector with a single element 2?

Both Sutter and Meyers recommend to avoid class designs where a initializer_list constructor overload can cause ambiguities to the programmer. Sutter 和 Meyers 都建议避免在类设计中, initializer_list构造函数重载会导致程序员产生歧义。

Sutter:萨特:

Guideline: When you design a class, avoid providing a constructor that ambiguously overloads with an initializer_list constructor, so that users won't need to use ( ) to reach such a hidden constructor.指导原则:当你设计一个类时,避免提供一个用 initializer_list 构造函数重载的构造函数,这样用户就不需要使用 ( ) 来访问这样一个隐藏的构造函数。

Meyers:迈尔斯:

As a result, it's best to design your constructors so that the overload called isn't affected by whether clients use parentheses or braces.因此,最好设计您的构造函数,以便调用的重载不受客户端使用括号还是大括号的影响。 In other words, learn from what is now viewed as an error in the design of the std::vector interface, and design your classes to avoid it.换句话说,从现在被视为 std::vector 接口设计中的错误中学习,并设计你的类来避免它。

But neither of them describe how vector should have been designed to avoid the problem!但是他们都没有描述应该如何设计vector来避免这个问题!

So here's my question: How should have vector been designed to avoid ambiguities with the initializer_list constructor overload (without losing any features)?所以这是我的问题:应该如何设计vector以避免与initializer_list构造函数重载(不丢失任何功能)的歧义?

I would take the same approach that the standard took with piecewise_construct in pair or defer_lock in unique_lock : using tags on the constructor:我将采用与标准中的piecewise_construct in pairdefer_lock in unique_lock相同的方法:在defer_lock上使用标签:

struct n_copies_of_t { };
constexpr n_copies_of_t n_copies_of{};

template <typename T, typename A = std::allocator<T>>
class vector {
public:
    vector(std::initializer_list<T>);
    vector(n_copies_of_t, size_type, const T& = T(), const A& = A());
    // etc.
};

That way:那样:

std::vector<int> v{10, 20}; // vector of 2 elems
std::vector<int> v2(10, 20); // error - not a valid ctor
std::vector<int> v3(n_copies_of, 10, 20); // 10 elements, all with value 20.

Plus, I always forget if it's 10 elements of value 20 or 20 elements of value 10, so the tag helps clarify that.另外,我总是忘记它是值 20 的 10 个元素还是值 10 的 20 个元素,所以标签有助于澄清这一点。

For the sake of completeness, one possible way (and not one I advocate) to avoid the ambiguity is to use static factory methods as a means to isolate the initializer_list constructor from the others.为了完整起见,避免歧义的一种可能方法(而不是我提倡的方法)是使用静态工厂方法作为将initializer_list构造函数与其他构造函数隔离的一种方法。

For example:例如:

template <typename T>
class Container
{
public:
    static Container with(size_t count, const T& value)
    {
        return Container(Tag{}, count, value);
    }

    Container(std::initializer_list<T> list) {/*...*/}

private:
    struct Tag{};
    Container(Tag, size_t count, const T& value) {/*...*/}
};

Usage:用法:

auto c1 = Container<int>::with(1, 2); // Container with the single element '2'
auto c2 = Container<int>{1, 2}; // Container with the elements {1, 2}

This static factory approach is reminiscent of how objects are allocated and initialized in Objective-C .这种静态工厂方法让人想起在 Objective-C中如何分配和初始化对象。 The nested Tag struct is used to ensure that the initializer_list overload is not viable.嵌套的Tag结构用于确保initializer_list重载不可行。


Alternatively, the initializer_list constructor can be changed to a static factory method, which allows you to keep the other constructor overloads intact:或者,可以将initializer_list构造函数更改为静态工厂方法,这允许您保持其他构造函数重载完整:

template <typename T>
class Container
{
public:
    static Container with(std::initializer_list<T> list)
    {
        return Container(Tag{}, list);
    }

    Container(size_t count, const T& value) {/*...*/}

private:
    struct Tag{};
    Container(Tag, std::initializer_list<T> list) {/*...*/}
};

Usage:用法:

auto c1 = Container<int>{1, 2}; // Container with the single element '2'
auto c2 = Container<int>::with({1, 2}); // Container with the elements {1, 2}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM