假设我重载了诸如~
和=
之类的C ++运算符,就像这样
Foobar& Foobar::operator~() const {
Foobar *result = new Foobar();
// compute *result based on *this
return *result;
}
Foobar& Foobar::operator=(Foobar& arg) {
// compute *this based on arg
// return *this for transitivity
return *this;
}
出于向后兼容性和性能原因,运算符必须返回Foobar&
,而不是Foobar
或指针。
然后,我班级的用户会写这样的东西:
Foobar obj0, obj1;
obj1 = ~obj0;
现在~
的归档new
已丢失,因此无法delete
new
,所以内存是否泄漏?如果有泄漏,如何设计,所以没有?
答案 0 :(得分:3)
现在从〜返回,这是新的丢失,所以没有办法 删除那个新的,所以没有内存泄漏?
是的,当前实现中几乎存在内存泄漏。或者至少有很高的机会。
如果有泄漏,如何设计这样就没有?
为什么不离开新的并去寻找一个本地对象并按值返回它?
这就是我的意思:
Foobar Foobar::operator~() {
Foobar result;
// compute *result based on *this
return result;
}
我认为您没有理由在示例中动态分配对象。
如果你真的需要一个动态分配的对象(我没有看到你为什么需要一个......),你可能想要使用智能指针。
编辑:
既然你澄清了性能可能是一个问题,我想指出(n)rvo以及@ VaughnCato / @ Potatoswatter已经提到过的语义(我希望你读过他们的答案)。我建议您阅读一些文章,例如Want speed? Pass by Value.和Move Constructor。有可能通过值返回可能不像使用(n)rvo + move语义那样考虑性能问题。我建议在之后使用优化实现移动语义和配置文件,以确定它是否真的是一个问题,因为它只是最干净的解决方案。
关于智能指针。我专门讨论了http://en.cppreference.com/w/cpp/memory/shared_ptr和http://en.cppreference.com/w/cpp/memory/unique_ptr,但是因为你在编辑过的答案中说过你想要一个参考而不是指针也许不是一个完美的解决方案。你可能想要了解的东西。
如果move /(n)rvo没有给出想要的结果,那么其他的可能性将是相当不洁净的,并且在我看来,像代理对象/全局容器(可能与智能指针结合)或其他东西一样容易出错。但我对这种东西并不擅长,你可以对@ VaughnCato / @ Potatoswatter的答案发表评论。因为我自己还是个初学者,所以我认为这是我可以向你推荐的。
答案 1 :(得分:3)
你是对的,它会导致内存泄漏。普通operator~
不是按引用返回,而是按值返回。运算符也应该是const以强制您不要改变操作数。使用这种方法不会有任何内存泄漏:
Foobar Foobar::operator~() const
{
Foobar result;
// compute *result based on *this
return result;
// OR
return Foobar(<stuff to compute result>);
}
答案 2 :(得分:1)
是的,未与匹配的new
配对的delete
会导致内存泄漏。
你永远不会定义&#34;容易。&#34;如果您想要自动删除,则无法进行。理论上,您可以返回一个智能指针代理对象,该对象定义operator Foobar &
并从其析构函数中调用delete
。也许您可以管理向后兼容需要Foobar &
作为返回类型的代码,但这将是一个糟糕的设计。该对象可能会过早地破坏自己,只提供一个悬空参考。
除了自动内容之外,你可以拿走你拥有的内容,确保将参考r
保留为返回值,始终记住调用delete &r;
,即使抛出异常。但从不这样做,除了成功调用operator ~
的结果。
根本问题是对Foobar &
类型有两种不同的语义要求。要释放内存,你必须将析构函数附加到引用,这是完全不可能的。
有许多真正的解决方案需要研究,例如C ++ 11移动语义,或代理容器类,以按值而不是Foobar
返回,从而避免性能问题。
答案 3 :(得分:1)
“由于性能方面的考虑,我无法返回每个值。在返回时复制整个类被认为太慢。我必须返回一个引用。”
但这就是你的代码显然所做的事情。它通过new创建一个Foobar
类型的新对象,并返回对它的引用。所以你实际上有你的另一个对象的副本。
如果你想完全避免复制,你将不得不去做表达模板之类的东西。
您可以通过中间对象实现此目的。
template<class Foo>
struct InvertedFoo
{
Foo const & to_invert;
InvertedFoo (Foo const &foo_to_invert)
: to_invert(foo_to_invert) { }
};
class Foobar
{
InvertedFoo<Foobar> operator~ (void) const
{
return InvertedFoo<Foobar>(*this);
}
Foobar& operator= (InvertedFoo<Foobar> const &arg)
{
// compute result based on arg.to_invert
}
};
请注意,这会将“invert”-logic转换为采用InvertedFoo参数的赋值运算符。
答案 4 :(得分:1)
通常,按值返回比分配新对象并返回对它的引用要快。返回值优化(RVO)或命名返回值优化(NRVO)消除了副本,并且使用new分配对象并不像您想象的那么便宜。如果你还没有,你应该在开启优化的两种方式进行分析,并确保你想要做的事情有好处。
如果你真的需要返回引用,一种可能性是使用全局容器:
std::list<Foobar *> global_foobars;
Foobar& Foobar::operator~() const {
Foobar *result = new Foobar();
global_foobars.push_back(result);
// compute *result based on *this
return *result;
}
然后您可以在适当的时间返回并删除global_foobars
中的对象。
使用C ++ 11,您可以更优雅地完成此任务:
std::list<Foobar> global_foobars;
Foobar& Foobar::operator~() const {
global_foobars.emplace_back();
return global_foobars.back();
}