Переопределение переменной-члена в C ++ - PullRequest
6 голосов
/ 18 ноября 2008

Я столкнулся с небольшой проблемой в коде C ++, который легче всего описать с помощью кода. У меня есть классы, которые что-то вроде:

class MyVarBase
{
}

class MyVar : public MyVarBase
{
    int Foo();
}

class MyBase
{
public: 
    MyBase(MyVarBase* v) : m_var(v) {}
    virtual MyVarBase* GetVar() { return m_var; }
private:
    MyVarBase* m_var;
}

У меня также есть подкласс MyBase, который должен иметь член типа MyVar, потому что он должен вызывать Foo. Перемещение функции Foo в MyVarBase не вариант. Имеет ли смысл сделать это:

class MyClass : public MyBase
{
public:
    MyClass(MyVar* v) : MyBase(v), m_var(v) {}
    MyVar* GetVar() { return m_var; }
private:
    MyVar* m_var;
}

Кажется, это работает, но выглядит очень плохо, и я не уверен, что это вызовет утечку памяти или сломает конструктор копирования. Мои другие варианты могут заключаться в том, чтобы назвать переменную MyVar в MyClass как-нибудь иначе, но иметь ее равной указателю m_var в базе, или шаблонизировать MyBase для типа MyVar.

Все эти варианты не кажутся идеальными, поэтому я хотел бы знать, сталкивался ли кто-либо еще с подобной ситуацией и есть ли хороший способ заставить его работать.

Ответы [ 3 ]

13 голосов
/ 18 ноября 2008

Правильный способ сделать это - иметь переменную только в базовом классе. Поскольку производный класс знает, что он должен иметь динамический тип MyVar, это вполне разумно:

class MyClass : public MyBase
{
public:
    MyClass(MyVar* v) : MyBase(v) {}
    MyVar* GetVar() { return static_cast<MyVar*>(MyBase::GetVar()); }
}

Поскольку MyVar является производным от MyVarBase, различные типы возврата GetVar все равно будут работать, если GetVar был виртуальным (как в данном случае). Обратите внимание, что с этим методом в MyBase не должно быть функции, которая могла бы сбрасывать указатель на что-то другое, очевидно.

Обратите внимание, что static_cast - правильный бросок в этом случае. Использование dynamic_cast, предложенное одним комментатором, скажет читателям и пользователям GetVar, что MyBase::GetVar() может вернуть указатель на объект, не относящийся к типу MyVar. Но это не отражает нашего намерения, так как вы проходите только MyVar. Быть последовательным - самая важная вещь в разработке программного обеспечения. То, что вы могли бы сделать, это утверждать, что оно не равно нулю. Он будет прерван во время выполнения с сообщением об ошибке в отладочных сборках вашего проекта:

MyVar* GetVar() { 
    assert(dynamic_cast<MyVar*>(MyBase::GetVar()) != 0);
    return static_cast<MyVar*>(MyBase::GetVar()); 
}
2 голосов
/ 18 ноября 2008

Не зная больше контекста, сложно сказать наверняка, но я бы пересмотрел, нужна ли вам эта иерархия классов в первую очередь. Вам действительно нужны классы MyVarBase и MyBase? Не могли бы вы сойти с рук композицию вместо наследования? Возможно, вам вообще не нужна иерархия классов, если вы шаблонируете функции и классы, которые работают с вышеуказанными классами. Может быть, CRTP тоже может помочь. Существует множество альтернатив использованию виртуальных функций (и в некоторой степени наследования в целом) в C ++.

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

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

0 голосов
/ 18 ноября 2008

Я думаю, что ваше упоминание шаблонов может быть хорошим вариантом, поэтому что-то вроде:

class MyVarBase
{
};

class MyVar : public MyVarBase
{
  int Foo();
};

template <class T> class MyBase
{
public: 
  MyBase(T* v) : m_var(v) {}
  T* GetVar() { return m_var; }

private:
  T* m_var;
};

class MyClass : public MyBase<MyVar>
{
public:
  MyClass(MyVar* v) : MyBase(v) {}
};

Однако это будет зависеть от того, какие классы вы можете изменить. Кроме того, определение «MyClass» может быть избыточным, если в нем нет других членов, кроме класса MyBase.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...