6

我有多个父母的 C++ 课程;每个父级定义一个具有通用名称但用途不同的函数:

class BaseA
{
    virtual void myFunc();  // does some task
};
class BaseB
{
    virtual void myFunc();  // does some other task
};
class Derived : public BaseA, public BaseB;

如果是这样,我将没有问题 - 我可以使用 using语句解决歧义,并且可以使用基类名称和范围解析运算符来选择调用哪个。

不幸的是,派生类需要同时覆盖它们:

class Derived : public BaseA, public BaseB
{
    virtual void BaseA::myFunc(); // Derived needs to change the way both tasks are done
    virtual void BaseB::myFunc();
}

这不起作用,不是因为它引入了新的歧义(尽管可能),而是因为

“错误 C3240:‘myFunc’:必须是‘BaseA’的非重载抽象成员函数”

“错误 C2838:成员声明中的非法限定名”

在不同的情况下,我可能只是重命名方法,或者按照编译器的建议将它们设为纯虚拟。然而,类层次结构和一些外部问题使得第一个选项极其困难,而第二个选项则不可能。

有人有建议吗?为什么限定符只允许用于纯虚方法?有什么方法可以同时覆盖虚拟方法并解决歧义?

4

4 回答 4

3

Microsoft 允许使用该语法(从 Visual C++ 2005 开始提供)。 他们还为托管代码引入了一种新的、更强大的语法

C++0x 中都没有包含任何一个。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2108.html


我认为这是一种解决方法:

class BaseA
{
protected:
    virtual void myFunc();  // does some task
};
class BaseB
{
protected:
    virtual void myFunc();  // does some other task
};
class ShimA : virtual BaseA
{
    virtual void myFunc() { myFuncA(); }
protected:
    virtual void myFuncA() { BaseA::myFunc(); }
};
class ShimB : virtual BaseB
{
    virtual void myFunc() { myFuncB(); }
protected:
    virtual void myFuncB() { BaseB::myFunc(); }
};
class Derived : public virtual BaseA, public virtual BaseB, protected ShimA, protected ShimB
{
     virtual void myFuncA() {}
     virtual void myFuncB() {}
};
于 2011-03-30T03:18:54.827 回答
2

您可以使用合成对象。

class Derived : public BaseB {        
    struct temp : public BaseA {
        virtual void myFunc() {
             d->BaseAMyFunc();
        }
        Derived* d;
    };
    temp t;
public:
    Derived() {
        t.d = this;
    }
    operator BaseA&() { return temp; }
    operator const BaseA&() const { return temp; }
    void myFunc(); // refers to BaseB::myFunc()
    void BaseAMyFunc(); // called when BaseA::myFunc() is called.
}

这不是特别整洁,也有一定的限制,但在某些情况下它确实有效。

于 2011-03-30T03:04:02.590 回答
1

这是多重继承的大问题之一。当您要继承多个同名函数以确定应该覆盖哪个函数时,总是会出现问题,请参阅The Diamond Problem。您不能同时覆盖两者,因为函数语法(函数名和运算符)必须是唯一的。

于 2011-03-30T03:03:21.887 回答
0

我意识到这个问题很老,但它有很多观点,如果你是接口的作者,有一个干净的方法来解决这个问题。

许多人认为虚拟接口应该具有公共的非虚拟功能,在内部遵循私有的虚拟功能(我同意他们的观点)。这有一些优点,其中之一是非虚拟名称可以具有不同的含义,因为它们与接口的绑定更加紧密:

struct BaseA
{
  virtual ~BaseA() = default;

  void func() 
  {
    handle_a_func();
  }

private:
  virtual void handle_a_func() = 0;
};

struct BaseB 
{
  virtual ~BaseB() = default;

  int func() const  // note the different signature and return type
  {
    handle_b_func();
  }

private:
  virtual int handle_b_func() const = 0;
};

// now provide an implementation

struct ABImpl : public BaseA, public BaseB
{
  ABImpl() {}

private:
  void handle_a_func() override final 
  {
    // alter some state
  }

  int handle_b_func() const override final
  {
    return _x;
  }

  int _x = 0;
};        

// now use the class
auto ab = make_shared<ABImpl>();

auto a = static_pointer_cast<BaseA>(ab);
auto b = static_pointer_cast<const BaseB>(ab);

a->func();  // uses A's mutable interface
auto x = b->func();  // uses B's const interface

这种方法的另一个优点是基类实现可以代表派生函数管理诸如互斥锁和哨兵之类的东西,例如:

struct base {

  void do() {
    std::unique_lock<std::mutex> l(_m);
    handle_do();
  }

private:
  virtual void handle_do() = 0;

  std::mutex _m;
};

另一个优点是一些有用的自由函数运算符只需要为整个类层次结构定义一次:

struct base
{
  void write_to(std::ostream& os) const {
    // lock a mutex here?
    handle_write(os);
  private:
    virtual void handle_write(std::ostream& os) const {
      os << "write base class info here\n";
    }
};

inline std::ostream& operator<<(std::ostream& os, const base& b) {
  b.write_to(os);
  return os;
}

struct derived : base {
private:
  virtual void handle_write(std::ostream& os) const override {
    base::handle_write(os);  // handle base class implementation
    std::cout << "write relevant data here. We could still be overridden safely\n";
  }
};

// write any derived class like this:
auto b = unique_ptr<base> { new derived() };
cout << "here is a kind-of b:\n" << *b << endl;
于 2014-09-16T10:35:08.847 回答