Один указатель, два разных класса в C ++ - PullRequest
6 голосов
/ 05 сентября 2011

Предположим, у меня есть две структуры a и b, каждая из которых содержит несколько переменных (большинство переменных - основные типы c ++, но не все).

Есть ли способ создать указатель с именем c, который может указывать на любой из них? Кроме того, есть ли способ создать набор, который может содержать один из них?

Спасибо

Ответы [ 8 ]

4 голосов
/ 05 сентября 2011

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

class Base {
public:
    int a;
};

class Sub1 : public Base {
public:
    int b;
};

class Sub2 : public Base {
public:
    int c;
};


int main() {
    Base* p = new Sub1;
    p.a = 1; // legal
    p.b = 1; // illegal, cannot access members of sub-class
    p = new Sub2; // can point to any subclass
}

То, что вы пытаетесь достичь, называется полиморфизм , и это одинфундаментальных концепций объектно-ориентированного программирования.Один из способов получить доступ к члену подкласса - уменьшить указатель.Когда вы делаете это, вы должны убедиться, что вы приведете его к правильному типу:

static_cast<Sub1*>(p).b = 1; // legal, p actually points to a Sub1
static_cast<Sub2*>(p).c = 1; // illegal, p actually points to a Sub1

Что касается вашего второго вопроса, используя технику, описанную выше, вы можете создать набор указателей на базу-класс, который может содержать экземпляр любого из подклассов (они также могут быть смешаны):

std::set<Base*> base_set;
base_set.insert(new Sub1);
base_set.insert(new Sub2);
4 голосов
/ 05 сентября 2011

Кроме того, есть ли способ создать набор, который может содержать один из них?

Взгляните на Boost.Any и Boost.Variant . Если у вас есть только 2 класса, то variant должно быть достаточно. Если вы планируете другие типы и не хотите перекомпилировать этот «набор», используйте any.

Затем используйте любой контейнер any или variant.

#include <boost/any.hpp>
#include <boost/variant.hpp>

#include <vector>

class A { };
class B { };
class C { };

int main()
{
    // any

    std::vector<boost::any> anies;
    anies.push_back(A());
    anies.push_back(B());

    A a0 = boost::any_cast<A>(anies[0]);
    A b0 = boost::any_cast<A>(anies[1]); // throws boost::bad_any_cast

    // variant
    std::vector<boost::variant<A,B> > vars;
    vars.push_back(A());
    vars.push_back(B());

    A a1 = boost::get<A>(vars[0]);
    A b1 = boost::get<A>(vars[1]); // throws boost::bad_get

    // and here is the main difference:
    anies.push_back(C()); // OK
    vars.push_back(C());  // compile error
}

Редактировать: наличие более 2 классов, конечно, возможно и для variant. Но расширение variant, чтобы оно могло содержать новый непредвиденный тип без перекомпиляции, не является.

3 голосов
/ 05 сентября 2011

Если a и b не связаны, то вы можете использовать тип void* или, что лучше, boost any.

Если a является суперклассом b , вместо него можно использовать a*.

1 голос
/ 05 сентября 2011

Если они оба наследуются от одного и того же типа, вы можете сделать это. Вот как работают OOP-фреймворки, все классы наследуются от Object.

0 голосов
/ 01 февраля 2015

Абстрактный класс !!!! - простые решения

Иметь базовый класс, который можно использовать как указатель на несколько производных подклассов. (не требуется литье)

Абстрактный класс определяется, когда вы используете в нем виртуальный метод. Затем вы реализуете этот метод в подклассе ... simple:

// abstract base class
#include <iostream>
using namespace std;

class Polygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
};

class Rectangle: public Polygon {
  public:
    int area (void)
      { return (width * height); }
};

class Triangle: public Polygon {
  public:
    int area (void)
      { return (width * height / 2); }
};

int main () {
  Polygon * ppoly1 = new Rectangle (4,5);
  Polygon * ppoly2 = new Triangle (4,5);
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  cout << ppoly1->area() << '\n';
  cout << ppoly2->area() << '\n';
  return 0;
}
0 голосов
/ 05 сентября 2011

Есть несколько способов сделать это:

  1. Использование более общего базового типа, если есть отношение наследования.
  2. Использование void * и явное приведение в соответствующих случаях.
  3. Создание класса-оболочки с отношением наследования, необходимого для # 1.
  4. Использование различающего контейнера через объединение.

Поскольку другие уже описали первые три варианта,Я опишу четвертый.По сути, различаемый контейнер использует тип union, чтобы использовать хранилище одного объекта для хранения одного из нескольких различных значений.Обычно такое объединение хранится в структуре вместе с перечислением или целочисленным типом для определения того, какое значение в настоящее время содержится в типе объединения.В качестве примера:

// Declarations ...
class FirstType;
class SecondType;

union PointerToFirstOrSecond {
   FirstType* firstptr;
   SecondType* secondptr;
};

enum FIRST_OR_SECOND_TYPE {
   FIRST_TYPE,
   SECOND_TYPE
};

struct PointerToFirstOrSecondContainer {
   PointerToFirstOrSecond pointer;
   FIRST_OR_SECOND_TYPE which;
};

// Example usage...

void OperateOnPointer(PointerToFirstOrSecondContainer container) {
    if (container.which == FIRST_TYPE) {
       DoSomethingWith(container.pointer.firstptr);
    } else {
       DoSomethingElseWith(container.pointer.secondptr);
    }
}

Обратите внимание, что в приведенном ниже коде «firstptr» и «secondptr» на самом деле представляют собой два разных представления одной и той же переменной (т. Е. Одно и то же место в памяти), поскольку объединения совместно используют пространствоcontent.

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

0 голосов
/ 05 сентября 2011

Хотя вы можете сделать это, что бы этот указатель означал?Если какая-либо часть вашего приложения удерживает указатель на 'или a, или b', он не сможет с этим ничего поделать, если вы не предоставите дополнительную информацию о типе.

В результате будет предоставлена ​​дополнительная информация о типев клиентском коде, таком как

if( p->type == 'a' ) {
   ... a-specific stuff
} else if( p->type == 'b' ) {
   ... b-specific stuff
} ...

, что не очень полезно.

Было бы лучше делегировать «специфичность типа» самому объекту, что является природой объектно-ориентированногодизайн, и C ++ имеет очень хорошую систему типов для этого.

class Interface {
 public:
    virtual void doClientStuff() = 0; // 
    virtual ~theInterface(){};
};

class A : public Interface {
    virtual void doClientStuff(){ ... a-specific stuff }
};

class B : public Interface {
    virtual void doClientStuff(){ ... b-specific stuff }
};

И тогда ваш клиентский код станет более не осведомленным о типах, так как переключение типов выполняется C ++ для вас.

void clientCode( Interface* anObject ) {
   anObject->doClientStuff();
}

Interface* i = new A();
Interface* j = new B();

clientCode( i );
clientCOde( j );
0 голосов
/ 05 сентября 2011

Просто определите общий суперкласс C и два подкласса A, B of C. Если A и B не имеют общей структуры (без общих атрибутов), вы можете оставить C пустым.

Определение:

A *a = new A();
B *b = new B();
C *c;

Тогда вы можете сделать как

c = a;

или

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