c++ 停止初始化器列表构造函数获取优先级的最佳实践

qv7cva1a  于 2023-10-20  发布在  其他
关注(0)|答案(1)|浏览(92)

我创建了一个可复制的最小示例:

class Vector {
    private:
        size_t sz;
        double* elem;
    public:
        Vector(size_t s) : sz{s}, elem{new double[s]} {}
        Vector(std::initializer_list<double> lst) : sz{lst.size()}, elem{new double[lst.size()]} {
            std::copy(lst.begin(), lst.end(), elem);
        }
        size_t size() const {return sz;}
        double& operator[](size_t i) {
            if (i < 0 || i >= sz)
                throw std::out_of_range("Vector::operator[](size_t): index out of range");
    
            return elem[i];
        }
};

int main() {
    Vector v1{3};
    Vector v2{v1.size()};
    std::cout << v2[0] << '\n';
    std::cout << v2[1] << '\n';
}

当我编译时,我得到警告:problem.cpp:25:22: warning: narrowing conversion of ‘v1.Vector::size()’ from ‘size_t’ {aka ‘long unsigned int’} to ‘double’.根据this问题(感谢@Joel的参考),这是因为列表初始化器构造函数优先。当我运行代码时,我可以看到这一点,因为我得到了错误:

terminate called after throwing an instance of 'std::out_of_range'
what():  Vector::operator[](size_t): index out of range

显然,v1v2都是作为单个元素向量构造的,因此调用v2[1]没有意义。虽然我已经发现了问题,但我被提到的问题并没有完全解决它。我的问题是:有没有推荐的方法来克服这种行为?()初始化是正确的吗?然而,这在某种程度上违背了统一初始化的目的。或者我应该将一些构造函数设置为explicit?或者有其他标准方法来规避此功能?

57hvy0tb

57hvy0tb1#

在这方面,统一初始化从根本上被打破。即使你修复了你的自定义容器,标准容器也会永远存在这个问题。
IMO,在任何地方使用它都没有好处。如果你担心隐式转换,-Wconversion(或你的编译器的等价物)将比统一初始化更好地捕捉它们。
但是如果你想在你的类中使用它,你可以使用标记分派(也就是一个额外的未使用的空结构参数):

struct WithSize {};
Vector(WithSize, size_t s) : sz{s}, elem{new double[s]} {}

那么Vector v1{3};将产生一个单元素向量,而Vector v1{WithSize{}, 3};将给你给予3个零。
或者我应该将一些构造函数设置为显式的?
std::vector(size_t)构造函数是explicit,这至少使= {...}语法在这方面是安全的(它总是调用initializer_list构造函数,而不会返回到size构造函数)。但这是另一回事,对你的问题没有帮助。
如果按照建议使用标记分派,则没有必要添加explicit

相关问题