假设我有一个简单的数据类:
struct Foo
{
int iData;
double dData;
};
我可以创建一个可以实现为的容器类:
版本1
struct Bar
{
std::vector<std::shared_ptr<Foo>> fooData;
};
或作为:
版本2
struct Bar
{
Bar(){}
Bar(Bar const& copy)
{
for(auto fooPtr: copy.fooData)
{
fooData.push_back(new Foo(*fooPtr));
}
}
~Bar()
{
for(auto fooPtr: fooData)
{
delete fooPtr;
}
}
Bar& operator=(Bar const& rhs)
{
if ( this == &rhs )
{
return *this;
}
for(auto fooPtr: fooData)
{
delete fooPtr;
}
fooPtr.clear(); // Had missed this in the original code.
for(auto fooPtr: rhs.fooData)
{
fooData.push_back(new Foo(*fooPtr));
}
return *this;
}
std::vector<Foo*> fooData;
};
承认版本1 比版本2 更简单,更易于维护。 但是,我有几个与代码方面正交的问题。
答案 0 :(得分:4)
是的,第一个选项更安全。也就是说,您的复制构造函数有两个内存泄漏源,复制赋值运算符有内存泄漏,简直就是错误。此外,第一个选项默认具有移动赋值和移动构造函数,从而提高了性能。 (如果您使用std::unique_ptr
代替,则表现更佳)
Bar(Bar const& copy)
{
for(auto fooPtr: copy.fooData)
{
fooData.push_back(new Foo(*fooPtr));
//what happens if `new Foo` throws a bad_alloc?
//or if `Foo(const Foo&)` changes and now throws something?
//or if `fooData.push_back` throws a bad_alloc?
//anything you 'new'd is leaked.
}
}
Bar& operator=(Bar const& rhs)
{
if ( this == &rhs )
return *this;
for(auto fooPtr: fooData)
delete fooPtr;
//You forgot this line:
//fooData.clear();
for(auto fooPtr: rhs.fooData) //sharth notes you wrote copy.fooData but that's obvious
fooData.push_back(new Foo(*fooPtr));
//what happens if `fooData.push_back` throws a bad_alloc?
//the `Foo` you just created is leaked.
return *this;
}
答案 1 :(得分:1)
版本1和版本2具有不同的语义。如果复制Bar对象,则版本1提供对数据的共享访问,而版本2则不提供。版本2也有一个微妙的内存泄漏;如果new
成功而push_back
失败,则对象会泄漏。
为什么不使用更简单的std::vector<Foo>
?在这种情况下,我没有看到使用指针(共享或原始)的任何理由。值语义比指针语义更容易推理。