Объявление условной переменной - PullRequest
5 голосов
/ 26 июля 2011

Я пришел из Python и у меня возникли проблемы с управлением типами в c ++.В Python я могу сделать что-то вроде этого:

if condition_is_true:
    x=A()
else:
    x=B()

, а в остальной части программы я могу использовать x, не заботясь о типе x, учитывая, что я использую методы и переменные-члены с тем же именем иаргументы (необязательно, чтобы A и B имели одинаковые базовые классы).Теперь в моем коде C ++ тип A соответствует

typedef map<long, C1> TP1;

, а B:

typedef map<long, C2> TP2;

, где:

typedef struct C1
{
    char* code;
    char* descr;
    int x;
...
}

и

typedef struct C2
{
    char* code;
    char* other;
    int x;
...
}

C1 и C2 имеют похожие члены, и в части кода, о которой я говорю, мне нужно использовать только те, которые имеют одно и то же имя / тип

Я хотел бы сделать что-то вроде:

if (condition==true)
{
    TP1 x;
}
else
{
    TP2 x;
}

каков правильный подход в c ++?

заранее спасибо

Ответы [ 7 ]

5 голосов
/ 26 июля 2011

Если условие известно во время компиляции, вы можете использовать std::conditional.Это полезно в общем коде.

typedef std::conditional<
    std::is_pointer<T>::value
    , TP1
    , TP2
>::type map_type;
map_type x;

(где тест составлен; здесь мы проверяем, является ли T типом указателя или нет)

Если условиене может быть известно до времени выполнения, тогда необходима некоторая форма динамического полиморфизма.Типичными примерами такого полиморфизма в C ++ являются подтипы boost::variant или, когда наступает пуш, boost::any.Какую из них выбрать * и как ее применять, зависит от вашего общего дизайна;мы не знаем достаточно.

*: очень вероятно, не будет boost::any.

2 голосов
/ 26 июля 2011

Хотя могут быть некоторые способы сделать это, они в основном хитрые и не обслуживаемые, как упоминал Дэймон.

Я рекомендую использовать функцию шаблона . Что вы действительно хотите, так это получить доступ к одному и тому же члену / функциям для другого класса. В функции шаблона вы можете получить доступ к объекту «общего типа», если тип обеспечивает операцию, которую вы используете в шаблоне.

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

struct TP1
{
  // common part
  int i;
  int j;
  // different part
  float f;
};

struct TP2
{
  // common part
  int i;
  int j;
  // different part
  double d;
};

template<typename CType>
void f(CType a)
{
  // as long as CType has variable i, j
  cout << a.i << endl;
  cout << a.j << endl;
}

int main(int argc, char* argv[])
{
  bool choice;

  // get a choice from console during runtime
  cin >> choice;

  if (choice)
  {
    TP1 x = {0, 0};
    f(x);
  }
  else
  {
    TP2 x = {1, 1};
    f(x);
  }

  return 0;
}
2 голосов
/ 26 июля 2011

У вас есть несколько вариантов.Если C1 и C2 оба типа POD, вы можете использовать объединение, которое разрешает доступ к общей начальной последовательности:

struct C1 { 
    // ....
};

struct C2 { 
    // ...
};

union TP {
    C1 c1;
    C2 c2;
};

union TP x;

std::cout << x.c1.code; // doesn't matter if `code` was written via c1 or c2.

Обратите внимание, что для сохранения начальной последовательности "общей", вы действительно хотите изменитьимена, поэтому второй член (descr / other) имеет одинаковое имя в обеих версиях структуры.

Если они не являются POD, вы можете использовать наследование, чтобы дать вам общий тип.

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

Если вы действительнонеобходимо разрешить тип во время выполнения, тогда шаблоны, вероятно, не будут работать - вам, вероятно, потребуется использовать объединение или базовый класс.

1 голос
/ 26 июля 2011

Если вам действительно нужны два разных типа, лучше всего было бы (если предположить, что классы похожи и имеют некоторые похожие функции-члены), чтобы иметь абстрактный класс, скажем, CBase (см. http://www.cplusplus.com/doc/tutorial/polymorphism/), а затем определить два подкласса C1 и C2 этого абстрактного класса.

Теперь ваш код можно записать следующим образом:

CBase *x;
if (condition) {
  x = new C1();
} else {
  x = new C2();
}

В случае, если вы не можете абстрагировать C1 и C2 в общий абстрактный класс, ну, тогда вам понадобятся две разные переменные и condition действует как ваш флаг, используя который вы можете узнать позже, какая переменная была заполнена и какая структура работать с.

0 голосов
/ 26 июля 2011

То, что вы пытаетесь сделать, невозможно в C ++. Переменные в C ++ имеют фиксированный тип, который определяется во время компиляции, и они не могут изменять тип во время выполнения. Но C ++ обеспечивает полиморфизм (который выглядит как динамические типы), который позволяет производным типам реализовывать функциональность базового класса, но единственный способ получить доступ к методам, специфичным для типа, - это привязать тип к базовому классу, если у вас есть тип, связанный с производный тип, то вы можете вызвать только реализацию этого типа *:

class Base
{
public: virtual void Func () = 0;
};

class C1 : public Base
{
public: virtual void Func () {}
};

class C2 : public Base
{
public: virtual void Func () {}
};

void SomeFunc ()
{
  C1 *c1 = new C1;
  C2 *c2 = new C2;
  Base *b;

  b = c1;
  b->Func (); // calls C1::Func
  b = c2;
  b->Func (); // calls C2::Func
}

Похоже, b изменил тип, но его фактический тип остался прежним, это всегда Base *, и ему можно присвоить только значения c1 и c2, потому что они имеют общую базу класс Base. Можно пойти другим путем:

Base *b = new C1;
C1 *c1 = dynamic_cast <C1 *> (b);

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

Base *b = new C2;
C1 *c1 = dynamic_cast <C1 *> (b);

c1 будет нулевым указателем, а не b. Но C1 и C2 все еще должны иметь общий базовый класс, чтобы это работало. Это не законно:

class Base {....}
class C1 : public Base {....}
class C2 {....} // not derived from Base!

Base *b = new C2; // no way to convert C2 to Base!
C2 *c2 = new C2;
b = dynamic_cast <Base *> (c2); // still won't work, C2 not a Base
b = new C1; // fine, C1 is a Base
C1 *c1 = new C1;
b = c1; // again, fine
c1 = dynamic_cast <C1 *> (b); // fine, C1 is a derived type of Base, this will work
c2 = dynamic_cast <C2 *> (b); // won't work, C2 is not a derived type of Base

Если C1 и C2 связаны (скажем, CSquare и CCircle), то общий базовый класс имеет смысл. Если они не связаны (скажем, CRoad и CFood), то общий базовый класс не поможет (это можно сделать, но это не очень логично). Выполнение первого (общий базовый класс) было хорошо описано в других ответах. Если вам нужно сделать последнее, то вам может понадобиться переосмыслить, как структурирован код, чтобы позволить вам сделать первое.

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

  • Конечно, в C ++ никогда не бывает так просто, и есть много вещей, которые могут запутать проблему. Например, производный тип может реализовать виртуальный метод общедоступного базового класса в частном порядке:
* +1034 * Пример: * * одна тысяча тридцать пять
class B
{
public:
  virtual void F () = 0;
};

class C : public B
{
private:
  virtual void F () { .... }
};

C *c = new C;
B *b = c;
b->F (); // OK
c->F (); // error
0 голосов
/ 26 июля 2011

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

if ( condition_is_true )
    wrapped_code( A() );
else
    wrapped_code( B() );

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

Более общей альтернативой является создание иерархии классов для переноса карты. Это решение немного многословно, но очень просто: просто определите базу класс с интерфейсом, который вы хотите, скажем:

class Map
{
public:
    virtual ~Map() {}
    virtual std::string getCode( long index ) const = 0;
    virtual std::string getDescr( long index ) const = 0;
    virtual int getX( long index ) const = 0;
};

, а затем шаблон, который вытекает из него:

template <typename T>   // Constraint: T must have accessible members code, other and x
class ConcreteMap : public Map
{
    std::map <long, T> myData;
public:
    virtual std::string getCode( long index ) const
    {
        return myData[index].code;
    }
    virtual std::string getDescr( long index ) const
    {
        return myData[index].descr;
    }
    virtual int getX( long index ) const
    {
        return myData[index].x;
    }
};

Ваш if становится:

std::unique_ptr<Map> x = (condition_is_true
                          ? std::unique_ptr<Map>( new ConcreteMap<C1> )
                          : std::unique_ptr<Map>( new ConcreteMap<C2> ));
0 голосов
/ 26 июля 2011

Я думаю, что вы можете сделать это путем полиморфизма во время выполнения.

class C_Base { /*all common variables*/ } ;
class C1 : public C_Base { ... };
class C2 : public C_Base { ... };

typedef map<long, C_Base *> TP;

{
...
    TP x;

    if (condition)
        /*use x as if values are C1 * */
    else
        /*other way round*/
}
...