Переопределение квалифицированных виртуальных методов

У меня есть класс С++ с несколькими родителями; каждый родитель определяет функцию с общим именем, но другой целью:

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: недопустимое полное имя в объявлении члена

При других обстоятельствах я мог бы просто переименовать методы или сделать их чисто виртуальными, как предлагает компилятор. Однако структура иерархии классов и ряд внешних проблем делают первый вариант чрезвычайно трудным, а второй невозможным.

У кого-нибудь есть предложение? Почему квалификаторы разрешены только для чисто виртуальных методов? Есть ли способ одновременно переопределить виртуальные методы и разрешить неоднозначности?


person John    schedule 30.03.2011    source источник
comment
Вы также можете переопределить поведение в отдельных классах: DerivedA : public BaseA, DerivedB : public BaseB, а затем Derived : public DerivedA, public DerivedB. Однако это не решает проблему неоднозначности   -  person Cameron    schedule 30.03.2011


Ответы (4)


Майкрософт допускает такой синтаксис (он доступен, начиная с 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() {}
};
person Ben Voigt    schedule 30.03.2011
comment
Спасибо за информацию о синтаксисе MS; к сожалению, он работает только с абстрактными базовыми классами (обратите внимание на ключевое слово __interface в их примере). - person John; 30.03.2011
comment
@John: управляемый вариант работает для виртуальных методов, унаследованных от базовых классов. В любом случае, взгляните на мое редактирование, я думаю, это должно решить вашу проблему. - person Ben Voigt; 30.03.2011
comment
Еще раз спасибо. Ваше предложение сработает, но особенности моей ситуации не позволяют мне его использовать. По сути, я пишу определение класса для некоторого обратно спроектированного кода, и я буквально не могу изменить структуру наследования или состав таблицы виртуальных функций (или, очевидно, использовать управляемый С++) - person John; 30.03.2011

Вы можете использовать объект композиции.

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.
}

Это не особенно удобно и несколько ограничено, но в некоторых случаях работает.

person Puppy    schedule 30.03.2011

Это одна из больших проблем с множественным наследованием. Всегда возникают проблемы, когда у вас есть несколько наследуемых функций с одним и тем же именем, чтобы определить, какая из них должна быть переопределена, см. Алмазная проблема. Вы не можете переопределить оба, так как синтаксис функции (имя функции и операторы) должен быть уникальным.

person Suroot    schedule 30.03.2011

Я понимаю, что этот вопрос старый, но у него было много просмотров, и есть простой способ решить эту проблему, если вы являетесь автором интерфейсов.

Многие люди считают, что виртуальные интерфейсы должны иметь общедоступные невиртуальные функции, которые внутренне подчиняются частным виртуальным функциям (я согласен с ними). У этого есть несколько преимуществ, одно из которых заключается в том, что невиртуальные имена могут иметь разные значения, поскольку они сильнее привязаны к интерфейсу:

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;
person Richard Hodges    schedule 16.09.2014