【问题标题】:C++ returning an object throw an interfaceC++ 返回一个对象抛出一个接口
【发布时间】:2011-07-11 14:18:11
【问题描述】:

我想设计一个通用接口,它有一个方法,可以让我将两个具有该接口的对象相乘,返回一个与结果相同的类的新对象。为了简单起见,我将只关注实现此接口的一个类。 问题:我的问题是返回的对象。 让我们看看:

拥有接口之前的目标:

class C {
  public:
  int get() { return v; }
  void set(int value) { v = value; }
  C operator * (C &l) {
    C r;
    r.set(get() * l.get());
    return r;
  }

  private:
  int v;
};

在这种情况下,operator* 方法返回一个 C 类型的对象,而不是一个引用(记住这一点)。它利用了返回值优化。 现在,让我们来解决问题: 在这种情况下我想设计一个界面:

struct I {
  virtual int get() = 0;
  virtual void set(int value) = 0;
  virtual I& operator * (I &l) = 0;
};

在这种情况下,我不能将返回的类型 I(按值)用于 operator* 方法,因为 I 是抽象的。好的,我们来解决问题:

class C : public I {
  public:
  int get() { return v; }
  void set(int value) { v = value; }
  I& operator * (I &l) {
    C r;
    r.set(get() * l.get());
    return r; //  warning: reference to local variable ‘r’ returned
  }

  private:
  int v;
};

这里的问题是我正在返回对局部变量的引用。

如何在不使用动态分配的情况下以正确的方式做到这一点?

提前致谢。

【问题讨论】:

  • 实际上您的第一个示例没有利用返回值优化 (RVO)。如果您的编译器能够进行命名返回值优化 (NRVO),那么在这种情况下可能会这样做。 return C(get() * l.get()); 出了什么问题?
  • 这个乘法运算符不是一个好主意。它不是可交换的。

标签: c++ interface return-value-optimization


【解决方案1】:

如果不使用动态分配,您无法以这种多态方式执行此操作。

你确定乘法在多态方面真的有意义吗?如果是这样,您将不得不返回一个动态分配的指针并管理它的生命周期(可能使用智能指针)。但在这种情况下,如果 operator* 不符合正常语义,因此您可能应该创建一个特殊函数来完成这项工作。

还请注意,您有一些不是 C++ 惯用的怪癖。 get 应该是 const 方法,operator* 的参数应该是 const 引用或值(没有人希望 operator* 修改其操作数之一,因为这违反了最小意外原则)。通常operator* 按值返回其结果,以防止在运算符未按值返回时混淆谁拥有任何分配的返回内存。

【讨论】:

  • +1 提到多态乘法没有多大意义。
  • 我意识到 C++ 在为这么好的运算符定义接口方面存在局限性。所以,我有一个更深层次的问题。由于 C++ 编译器使用 RVO 或 NRVO,在将取消引用 (I&) 返回到本地对象时保持相同的意识形态有什么问题?这实际上是返回环境中的一个对象,但在方法内部使用。
【解决方案2】:

要么返回指向动态分配对象的指针 (I*),要么按值返回 C

返回引用是可能的,但它会造成混淆,因此不要执行以下操作:

virtual I &operator*(I &l) {
    C *r = new C();
    r->set(get() * l.get());
    return *r; //  warning: reference to local variable ‘r’ returned
}

这里的问题是,虽然返回值看起来像一个值,但它仍然必须是delete'd,语法不寻常的delete &r;

【讨论】:

  • @Armen Tsirunyan:点击提交太快了,抱歉。
【解决方案3】:

我相信在这种情况下你应该使用operator*= 而不是operator*

operator*=中你返回了一个对*this的引用,它不是一个局部变量,所以可以返回。

operator* 的情况下,它应该是 const 并且您需要按值返回(好吧,语言不会强迫您这样做,但是如果您正在做乘法,这很可能是最明智的做法做)。我不确定如何使这样的东西与虚函数一起工作,我通常会使用模板(尽管使用这种方法会丢失运行时多态性)。

【讨论】:

    【解决方案4】:

    编辑:你想要的都是可能的......但是这是一个非常糟糕的主意。

    不分配内存的要求是完全不可能的。乘法需要创建一个新对象(根据定义),并且需要在堆上创建一个多态对象(如果您想避免切片并通过接口引用它)。

    但是,如果您仍想使用多态 a,b,c 编写 I* result = a*b*c,您可以通过使用代理对象并使用一些隐式转换来完成此操作..

    你可以创建一个带有隐式转换的代理对象:

       struct Iref {
          I* ref;
          Iref(I* aref):ref(aref){};
          operator I*() { return ref; }   
          Iref operator * (I& b);
          Iref operator * (Iref b);
          Iref operator * (I* b);
       };
    

    使用此代理和适当编写的方法,您最终可以得到:

    int main()
    {
      C a(1), b(2), c(3);
      I* result = a*b*c;
      return 0;
    }
    

    剩下的问题是固有的内存泄漏。您可以在应用程序中使用垃圾收集器。我对这些没有经验,我只知道这是可能的。或者你让代理对象处理内存释放。

    这个相当复杂的技巧应该表明operator* 和多态性一起不是一个好主意。还有其他问题,就像多态性一样,您最终可能会得到 a * bb * a 的不同结果。

    【讨论】:

    • 是的,这就是重点,使用返回环境中的 retval。但我需要介绍 operator* 方法。也许,我将放弃接口并继续使用没有接口的类。感谢所有的答案:)
    【解决方案5】:

    1 - 您不能返回类型为“I”(抽象类型)的对象实例
    2 - 如果你想返回一个对象,而不是一个指针,你应该使用正常的返回类型('C')。其他方式:使“我”不抽象并返回“我”。
    3 - 推荐的方式:只返回 I* 并且不要忘记稍后删除对象。

    【讨论】:

      猜你喜欢
      • 2011-11-12
      • 2020-10-24
      • 1970-01-01
      • 2011-05-24
      • 1970-01-01
      • 2015-06-06
      • 1970-01-01
      • 1970-01-01
      • 2020-02-19
      相关资源
      最近更新 更多