Conditional variable declaration

2019-06-23 19:20发布

问题:

I'm coming from Python and I have some problem with managing types in c++. In Python I can do something like this:

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

and in the rest of the program I can use x without caring about the type of x, given that I use methods and member variables with the same name and arguments (not necessary that A and B have the same base classes). Now in my C++ code type A corresponds to

typedef map<long, C1> TP1;

and B to:

typedef map<long, C2> TP2;

where:

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

and

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

C1 and C2 have similar members and in the part of code I'm talkin of I only have to use the ones with the same name/type

I would like to do something like:

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

what is the correct approach in c++?

thanks in advance

回答1:

If the condition is known at compile-time, you can use std::conditional. This is useful in generic code.

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

(where the test is made-up; here we're testing whether T is a pointer type or not)

If the condition cannot be known until runtime, then some form of dynamic polymorphism is needed. Typical instances of such polymorphism in C++ are subtyping, boost::variant or when push comes to shove, boost::any. Which one you should pick* and how you should apply it depends on your general design; we don't know enough.

*: very likely not to be boost::any.



回答2:

You have a couple of choices. If C1 and C2 are both POD types, you could use a union, which allows access to the common initial sequence:

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.

Note that to keep the initial sequence "common", you really want to change the names so the second member (descr/other) has the same name in both versions of the struct.

If they're not PODs, you can use inheritance to give you a common type.

C++, however, doesn't have a direct counterpart to Python's famous "duck typing". While templates provide type erasure (to at least some degree), you'd end up with kind of the reverse of what you're doing in Python. Instead of the variation between the two types happening where you deal with the variable, you'd allow code to deal with two different types that had common syntax. This is different, however, in that it requires that the compiler be able to resolve the actual type being used with any particular template at compile time, not just run time.

If you really need to resolve the type at run time, then templates probably won't work -- you'll probably need to use a union or base class.



回答3:

Although there may be some ways to do it, they're mostly tricky and not maintainable, just as Damon mentioned.

I recommend you to use template function. What you really want is to access the same member/functions for different class. In template function, you can access the object of a "general type" as long as the type provides the operation you use in the template.

For example, in your case you can simply extract the common parts into a template function like this.

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


回答4:

If you really need two different types, the best thing to do would be (assuming the classes are similar and has some similar member functions) to have an abstract class, say, CBase (see http://www.cplusplus.com/doc/tutorial/polymorphism/) and then define two subclasses C1 and C2 of this abstract class.

Now your code can be written as follows:

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

In case you can not abstract C1 and C2 into a common abstract class, well, then you'll need two different variables and condition acts like your flag using which you can know later which variable has been populated and which structure to work with.



回答5:

i think you can do it by runtime polymorphism.

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*/
}


回答6:

In order to use two different types through a common variable, the types must have a common base class. Since what you have is two different types which you can't change, and which don't have a common base class, you need some sort of duck typing. In C++, only templates use duck typing: one solution would be to move all of the code after the condition into a separate function template, to which you pass the results, and then write something like:

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

Depending on the code that actually follows the condition, this may be more or less convenient.

A more general alternative is to create your class hierarchy to wrap the maps. This solution is a bit verbose, but very easy: just define a base class with the interface you want, say:

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

, and then a template which derives from it:

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

Your if then becomes:

std::unique_ptr<Map> x = (condition_is_true
                          ? std::unique_ptr<Map>( new ConcreteMap<C1> )
                          : std::unique_ptr<Map>( new ConcreteMap<C2> ));


回答7:

What you're trying to do is not possible in C++. Variables in C++ have a fixed type which is defined at compile time and they can't change type at run time. But C++ does provide polymorphism (which looks like dynamic types) which allows derived types to implement base class functionality, but the only way to access type specific methods is to have a type bound to the base class, if you have a type bound to the derived type then you can only call that type's implementation*:

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
}

It looks like b has changed type, but it's actual type has remained the same, it is always a Base * and it can only be assigned the value c1 and c2 because they share a common base class Base. It is possible to go the other way:

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

but it requires the dynamic_cast and that requires something called RTTI (Run-Time Type Information) which provides the compiled code a way to check that b is actually pointing to a C1 type. If you were to do the following:

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

c1 would be the null pointer, not b. But C1 and C2 must still have a common base class for this to work. This is not legal:

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

If C1 and C2 are related (say, CSquare and CCircle) then a common base class makes sense. If they are not related (say, CRoad and CFood) then a common base class won't help (it can be done, but it's not very logical). Doing the former (common base class) has been well described in the other answers. If you need to do the latter, then you may need to re-think how the code is structured to allow you to do the former.

It would help if you could expand on what you want to do with x. Since x is a container, do you just want to do container related operations?

  • Of course, things are never that easy in C++ and there are many things that can confuse the issue. For example, a derived type may implement a public base class virtual method privately:

Example:

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