2

假设我重载了诸如~and之类的 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;

现在返回 from ~,它new丢失了,所以没有办法deletenew所以没有内存泄漏?如果有泄漏,如何设计这样就没有了?

4

5 回答 5

3

现在从~的返回,新的丢失了,所以没有办法删除那个新的,所以不是内存泄漏吗?

是的,您当前的实现中几乎存在内存泄漏。或者至少是一个很高的机会。

如果有泄漏,如何设计这样就没有了?

为什么不把新的东西放在一边去寻找一个本地对象并按值返回它呢?

这就是我的意思:

Foobar Foobar::operator~() {
  Foobar result;
  // compute *result based on *this
  return result;
}

我看不出有任何理由让您在示例中动态分配对象。

如果你真的需要一个动态分配的对象(我看不出你需要一个的任何理由......)你可能想要使用智能指针。


编辑:

既然您澄清了性能可能是一个问题,我想向您指出 (n)rvo 以及 @VaughnCato/@Potatoswatter 已经提到的移动语义(我希望您已经阅读了他们的答案)。我建议阅读一些文章,例如想要速度?按值传递。移动构造函数。很有可能,按值返回可能不像您对 (n)rvo + move 语义所想的那样严重。我建议实施移动语义和配置文件,并在之后进行优化,以确定它是否真的是一个问题,因为它只是最干净的解决方案。

关于智能指针。我是在专门谈论http://en.cppreference.com/w/cpp/memory/shared_ptrhttp://en.cppreference.com/w/cpp/memory/unique_ptr但既然你在编辑的答案中说你想要一个参考而不是可能不是完美解决方案的指针。你可能想要调查的东西。

如果 move/(n)rvo 没有给出想要的结果,其他可能性将是相当不干净的,并且在我看来,代理对象/全局容器(可能与智能指针结合使用)或其他东西容易出错。但我对这种东西还不擅长,你可能会对@VaughnCato/@Potatoswatter 的回答发表评论。由于我自己还是一个初学者,我想这就是我能给你的所有建议。

于 2013-07-15T01:19:51.810 回答
3

你是对的,它会导致内存泄漏。法线operator~不是按引用返回,而是按值返回。运算符也应该是 const 以强制您不要改变操作数。使用这种方法不会有任何内存泄漏:

Foobar Foobar::operator~() const
{
  Foobar result;
  // compute *result based on *this
  return result;
  // OR
  return Foobar(<stuff to compute result>);
}
于 2013-07-15T02:23:47.807 回答
1

是的,new未与匹配项配对delete会导致内存泄漏。

你从来没有定义“容易”。如果你想自动删除,你不能。理论上,您可以返回一个智能指针代理对象,该对象定义并从其析构函数operator Foobar &调用。delete也许您可以管理与需要Foobar &返回类型的代码的向后兼容性,但这将是糟糕的设计。该对象可能会过早地破坏自己,只提供一个悬空引用。

除了自动的东西,你可以拿走你拥有的东西,一定要保留r对返回值的引用,并且永远记得调用delete &r;,即使抛出异常也是如此。但永远不要这样做,除非成功调用operator ~.

根本问题是您对 type 有两种不同的语义要求Foobar &。要释放该内存,您必须将析构函数附加到引用,这是完全不可能的。

有许多真正的解决方案需要研究,例如 C++11 移动语义,或通过值而不是按值返回的代理容器类Foobar,从而回避性能问题。

于 2013-07-15T04:39:58.143 回答
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
  }
};

请注意,这会将“反转”逻辑转换为采用 InvertedFoo 参数的赋值运算符。

于 2013-07-15T04:42:28.453 回答
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();
}
于 2013-07-15T04:43:25.800 回答