这可能是一个简单的问题,但我有template class
:
template<typename Type>
class Array {
size_t n;
Type* buff;
public:
Array(size_t n_): n(n_), buff(new Type[n]) {}
};
代码来自课程pdf文件,其中buff(new Type[n])
不安全。我不明白为什么它不安全,是不是size_t一般都没有签名?我可以举例说明它可能有编译和/或运行时错误吗?
答案 0 :(得分:12)
代码是&#34;不安全&#34;因为它依赖于n
之前构建的buff
。这种依赖性会增加代码的脆弱性。
当你构造类的成员时,它们是按照在类中声明的顺序构造的,而不是在成员初始化列表中如何调用它们,所以如果代码被更改为
template<typename Type>
class Array {
Type* buff;
size_t n;
public:
Array(size_t n_): n(n_), buff(new Type[n]) {}
};
然后,当您执行buff(new Type[n])
时,n
未初始化且您有未定义的行为。
答案 1 :(得分:3)
首先,顺序,执行构造函数的初始化不是由它们写下来的顺序决定的,而是由初始化字段出现在代码中的顺序决定:
class Array {
size_t n;
Type* buff;
public:
Array(size_t n_): n(n_), buff(new Type[n]) {}
};
这里首先将n初始化然后buff。
class Array {
Type* buff;
size_t n;
public:
Array(size_t n_): n(n_), buff(new Type[n]) {}
};
现在,第一个buff将被初始化,然后是n,因此在这种情况下,n没有定义的值。
使用构造函数的初始化列表是很好的做法,但要小心,不要在订单上创建任何假设。
一般来说,不要拥有原始指针是个好主意。如果您使用智能指针,则不能忘记发布数据。
在特定情况下,您可能还想使用std :: vector而不是C样式数组。它以线程安全的方式为您处理所有分配,重新分配,发布等。看起来你正在尝试编写类似于你自己的std :: vector的东西。请仅为教育目的这样做。始终更喜欢生产代码中的标准实现。很长一段时间你可能不会让它变得更好。如果你这样做,你会在这里提出不同的问题; - )
答案 2 :(得分:3)
首先,你有内存泄漏。但问题可能与此无关。所以我们假设您有一个析构函数来释放数组。
template<typename Type>
class Array {
size_t n;
Type* buff;
public:
Array(size_t n_): n(n_), buff(new Type[n]) {}
~Array() { delete[] buff; }
};
现在此特定代码非常安全。分配n_
时不会抛出异常,初始化顺序正确,buff
是类中唯一的原始指针。但是,当您开始扩展类并编写更多类时,内存泄漏的风险会增加。
想象一下,您需要再向class Array
添加一个成员:
template<typename Type>
class Array {
size_t n;
Type* buff;
SomethingElse xyz;
public:
Array(size_t n_): n(n_), buff(new Type[n_]), xyz(n_) {}
~Array() { delete[] buff; }
};
如果SomethingElse
的构造函数抛出,则为buff
分配的内存将泄漏,因为将永远不会调用析构函数~Array()
。
Modern C ++调用诸如Type* buff
原始指针之类的指针,因为您自己负责释放存储(考虑异常),并引入std::unique_ptr
和{{}等工具。 {3}}可以自动处理存储释放。
在现代C ++中,你可以像这样编写你的类:
template<typename Type>
class Array {
size_t n;
std::unique_ptr<Type[]> buff;
public:
Array(size_t n_): n(n_), buff(new Type[n_]) {}
};
注意没有析构函数。 unique_ptr
将负责为您调用delete
。
请注意,初始化列表中的类成员也不依赖(只需编写new Type[n_]
而不是new Type[n]
,这样可以使代码更加健壮)
答案 3 :(得分:0)
C ++ 98 Standard 12.6.2.5.4(我不希望新版本放宽这个。)
- 然后,非静态数据成员应按其顺序初始化 在类定义中声明(再次无论顺序如何) 记忆初始化者。)
因此,初始化顺序是根据这个定义的。
如果您想要一个如何崩溃的示例,只需制作sizeof(Type)*n
&gt;系统中的总内存。
答案 4 :(得分:-1)
在初始化列表中调用new运算符是不安全的。 如果new失败,则不会调用Array的析构函数。 这是一个类似的问题。 Are there any issues with allocating memory within constructor initialization lists?