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

时间:2015-02-06 20:09:15

标签: c++ c++11 vector initializer-list

当类的构造函数重载为std::initializer_list时,即使其他构造函数重载看起来更好匹配,此重载也会优先。 Sutter的GotW#1第2部分以及Meyers的Effective Modern C++第7项详细描述了这个问题。

这个问题表现出来的典型例子是大括号初始化std::vector

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

Sutter和Meyers都建议避免使用initializer_list构造函数重载会导致程序员不明确的类设计。

萨特:

  

指南:设计类时,请避免提供构造函数   使用initializer_list构造函数模糊地重载,以便   用户不需要使用()来达到这样一个隐藏的构造函数。

迈尔斯:

  

因此,最好设计你的构造函数   被调用的重载不受客户端是否使用括号或   括号。换句话说,从现在被视为错误的东西中学习   std :: vector接口的设计,并设计你的类   避免它。

但他们都没有描述 vector应如何设计以避免此问题!

所以这是我的问题:如何设计vector以避免initializer_list构造函数重载的歧义(不丢失任何功能)?

2 个答案:

答案 0 :(得分:8)

我会采用标准与piecewise_construct中的pairdefer_lock中的unique_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.
};

那样:

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.

另外,我总是忘记它是10个元素值20或20个元素值10,所以标签有助于澄清。

答案 1 :(得分:1)

避免歧义的一种可能方法是使用静态工厂方法将initializer_list构造函数与其他构造函数隔离开来。

例如:

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) {/*...*/}
};

用法:

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}

这种静态工厂方法让人想起对象是如何allocated and initialized in Objective-C的。嵌套的Tag结构用于确保initializer_list重载不可行。


或者,可以将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) {/*...*/}
};

用法:

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}