2016-12-21 1 views
0

У меня есть несколько подобных классов, наследующих от одного базового класса/интерфейса (базовый класс 1), и они имеют пару похожих функций, но также имеют свои собственные функции. Все они также имеют свои собственные переменные-члены разных классов, и каждый из них наследуется от одного базового класса/интерфейса (базовый класс 2). Можно ли определить переменную в базовом классе 1 типа Base class 2, а затем в фактической реализации классов, использующих Base class 1, иметь переменную типа Base class 2, ее собственный тип. Своего трудно объяснить, поэтому упрощенный пример ниже.Родительский класс, ссылающийся на дочернюю переменную

//Base-Class 1 
class Shape 
{ 
    public Shape() {} 
    ShapeExtra m_var; 

    //The common functions 
    public GetVar(){ return m_var; } 
} 

class Circle : Shape 
{ 
    public Circle() { m_var = new CircleExtra(); } 

    public void CircleFunc() 
    { 
     m_var.CircleExtraFunc(); 
    } 
} 
class Triangle : Shape 
{ 
    public Triangle() { m_var = new TriangleExtra(); } 

    public void TriangleFunc() 
    { 
     m_var.TriangleExtraFunc(); 
    } 
} 
. 
. 
. 



//Base_Class 2 
class ShapeExtra 
{ 
    public ShapeExtra() {} 
} 

class CircleExtra : ExtraClass 
{ 
    public CircleExtra() {} 
    void CircleExtraFunc() {//Do stuff} 
} 
class TriangleExtra : ExtraClass 
{ 
    public TriangleExtra() {} 
    void TriangleExtra() {//Do stuff} 
} 
. 
. 
. 

Итак, я нужен m_var в дочерних классах, которые будут храниться как своей собственной уникальной версии. Потому что прямо сейчас (без дополнительной CircleExtra m_var;), то GetVar() работает, но в CircleFunc, m_var еще тип ShapeExtra, и, таким образом, не знает, что существует CircleExtraFunc. Я мог бы накладывать m_var каждый раз, когда я хотел это сделать, но это повторяется и не стоит в моем реальном случае. Есть ли способ использовать функции в уникальных классах, основанные на ShapeExtra, сохраняя функцию GetVar() в Shape?

Пожалуйста, задавайте вопросы, если есть что-то, что я забыл.

+0

В чем вопрос? –

+0

Добавлено: «Есть ли способ сделать это без кастинга каждый раз?» –

+2

«Мне нужно m_var в дочерних классах, чтобы сохранить его как свою уникальную версию». Это уже есть. 'm_var' в дочерних классах не имеет абсолютно никакого отношения к' m_var' в базовом классе. Базовый класс имеет свой собственный 'm_var', так же как и каждый дочерний класс. Они уникальны и отличаются друг от друга. В отличие от виртуальных функций, члены класса не переопределяют членов одного и того же имени в базовом классе. Ваш вопрос непонятен. –

ответ

1

Просто с наследованием и без использования указателей это не представляется возможным, поскольку C++ является статически и строго типизированным языком.

Вы можете наследовать как переменную, так и функцию, но вам нужно указать значение возврата функции.

Вы также можете переопределить функцию, чтобы вернуть ее конкретному типу, но тогда вам нужно включить переменную внутри функции.

Вы также можете объявить тот же var с конкретным классом в подклассах, но тогда вы просто спрячете переменную в суперклассе и ничего не наследуете.

Я предпочел бы найти решение с использованием шаблонов. Сделайте тип переменной типом шаблона и расширьте шаблон, используя конкретный тип в подклассах. Это будет отлично.

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

template <class S> 
class Shape { 
    S m_var; 
    //...... 
public: 
    S var() { 
     return m_var; 
    } 
    //....... 
} 

class Circle: Shape <CircleExtra> { 
    // var method returns CircleExtra 
    //...... 
} 

Edit: Что касается некоторых комментариев, чтобы виртуальный вызов метода, можно использовать коррелированные возвращаемые типы. Что-то вроде следующего примера.

class Shape { 
public: 
    virtual ShapeExtra *var() = 0; 
} 

template <typename SE> 
class ConcreteShape: Shape { 
public: 
    virtual SE *var() { 
     return &m_var; 
    } 
// Constructor, etc. 

private: 
    SE m_var; 
} 

или какой-либо вариант. Теперь конкретные формы могут выиграть от расширения шаблона, если SE * коррелирует с ShapeExtra * (параметр типа расширяет ShapeExtra). И вы можете прозрачно использовать метод через интерфейс Shape.

+0

Этот подход не позволяет сценарии, в которых «Shape» необходимо манипулировать как базовый класс , –

+0

Я думаю, что это, вероятно, лучшее решение моей проблемы. Спасибо за помощь! –

+0

@KirillKobelev 'Shape' является своего рода интерфейсом в моем сценарии и никогда не будет его собственным экземпляром. Я не полностью уточнил это, извините. –

1

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

#include <iostream> 
#include <memory> 
using namespace std; 

//Extras 
class ShapeExtra 
{ 
    public: 
    ShapeExtra() {} 
    void ShapeFunc() { std::cout << "Shape"; } 
    virtual ~ShapeExtra() = default; //Important! 
}; 

class Shape 
{ 
    public: 
    std::unique_ptr<ShapeExtra> m_var; 

    //require a pointer on construction 
    //make sure to document, that Shape class takes ownership and handles deletion 
    Shape(ShapeExtra* p):m_var(p){} 

    //The common functions 
    ShapeExtra& GetVar(){ return *m_var; } 
    void ShapeFunc() {m_var->ShapeFunc();} 
}; 


class CircleExtra : public ShapeExtra 
{ 
    public: 
    void CircleExtraFunc() {std::cout << "Circle";} 
}; 

class Circle : public Shape 
{ 
    CircleExtra* m_var; 

    public: 
    Circle() : Shape(new CircleExtra()) { 
     m_var = static_cast<CircleExtra*>(Shape::m_var.get()); 
    } 

    void CircleFunc() 
    { 
     m_var->CircleExtraFunc(); 
    } 
}; 


int main() { 
    Circle c; 
    //use the ShapeExtra Object 
    c.GetVar().ShapeFunc(); 
    //call via forwarded function 
    c.ShapeFunc(); 
    //call the circleExtra Function 
    c.CircleFunc(); 
    return 0; 
} 

Test it on ideone

Обратите внимание на использование указателей и виртуального деструктора:

  • Используя виртуальный деструктор в ShapeExtra базового класса , вы можете уничтожить объект любого производного класса, используя ShapeExtra*. Это важно, потому что
  • с помощью std::unique_ptr<ShapeExtra> вместо простого C-указателя мы гарантируем, что объект будет удален правильно при уничтожении Shape.
  • Возможно, это хорошая идея документировать это поведение, то есть Shape берет на себя ответственность за ShapeExtra*. Что означает, прежде всего, что мы не удалять CirleExtra* в Circle деструктора
  • Я решил здесь, чтобы потребовать ShapeExtra* на строительство, но его также можно использовать только std::unique_ptr::reset() позже и проверить nullptr на разыменования заказ Shape::m_var
  • Строительство заключается в следующем: при вызове конструктора Circle мы сначала создаем новый CircleExtra, который мы передаем Shape, прежде чем, наконец, будет выполнен конструктор Circle.
  • заказ Разрушение Circle первый (был создан в прошлом), то Shape который также Уничтожает ShapeExtra для нас, в том числе (с помощью виртуальной функции) CircleExtra
1

Я бы рекомендовал следующий подход:

class ShapeExtra 
{ 
public: 

    virtual ~ShapeExtra() { } 
    virtual void SomeCommonShapeFunc() { std::cout << "Shape"; } 
}; 

class Shape 
{ 
public: 

    virtual ShapeExtra &GetVar() = 0; // Accessor function. 
}; 

Обратите внимание, что класс Shape не имеет каких-либо элементов данных на всех. После этого для каждого производного класса вам необходимо:

class CircleExtra : public ShapeExtra 
{ 
public: 

    void SomeCommonShapeFunc() { std::cout << "Circle"; } 
}; 

class Circle : public Shape 
{ 
    CircleExtra m_var; // Data member with circle specific class. 

public: 

    virtual ShapeExtra &GetVar() { return m_var; } 
}; 

Реализация виртуального метода в Circle будет возвращать ссылку на базовый класс ShapeExtra. Это позволит использовать этот дополнительный в базовом классе.

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