3

I'm trying to make each instance of a class (named Caller here) have an instance of another class (Target). The point is that the second class has numerous children and I need to be able to have the Caller class switch among them at will. I have tried several ways, but not one gave me any desirable results. Current code:

class Target
{
public:
    virtual void do_something()
    { log_message("NO!"); }
};

class TargetChild : public Target
{
public:
    virtual void do_something()
    { log_message("YES!"); }
};

class Caller
{
private:
    Target target;

public:
    void call_target()
    { target.do_something(); }
    void set_target(Target set_target)
    { target = set_target; }
};

int main( int argc, const char* argv[] )
{
    TargetChild targetChild;

    Caller caller;
    caller.call_target();
    caller.set_target(targetChild);
    caller.call_target();
}

The wanted result in the log file is "NO! YES!" but instead it writes the NO! twice. I can't really see what's wrong with it.

Graham Borland
  • 60,055
  • 21
  • 138
  • 179

5 Answers5

6

You cannot change the type of an object in C++. You can only create, destroy, copy, and (in C++11) move data. In this case, you've copied the data from the subclass object to the base class object. This copied an empty subset of an empty subset. The problem you observed is called "slicing."

Probably what you want is a member containing a function pointer that can be changed. virtual gets you a function pointer but it can't be changed. Try std::tr1::function, boost::function or C++11 std::function.

Or, if you really want to go the virtual route, use a Target * pointer. Best to use a smart pointer class such as unique_ptr< Target > (again Boost/TR1/C++11) or std::auto_ptr< Target > (old-fashioned C++03). You can also do it yourself with new and delete, but such code doesn't really work as well, and is only suitable for a little educational tinkering.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
1

Your code is suffering from object slicing. Even though targetChild in main is a TargetChild, it's passed by value to Caller::set_target, which means it's copied to a local variable which has type Target. To get around object slicing in general, you must use pass-by-reference or use pointers.

In this case, since you wish for Caller to access the passed object outside the Caller::set_target method, you should use a pointer. References alone won't work because though you could make Caller::target a reference, you couldn't change the object it referred to.

Pointers introduce memory management problems, as you must ensure the TargetChild is deallocated (otherwise the program has a memory leak), but not too soon (which will cause access violations, most likely crashing your program). The boost library has various smart pointer classes to make this easier. The C++ standard auto_ptr class could also be used, if only one other class should own the TargetChild instance at any one point in time.

Community
  • 1
  • 1
outis
  • 75,655
  • 22
  • 151
  • 221
0

Use pointers instead. More about polymorphism: http://www.cplusplus.com/doc/tutorial/polymorphism/

...
class Caller
{
    private:
        Target* target;

    public:
        Caller() { target = NULL; }
        void call_target() { if (target != NULL) target->do_something(); }
        void set_target(Target* set_target) { target = set_target; }
};

int main( int argc, const char* argv[] )
{
    ....
    caller.set_target(&targetChild);
    ...
}
Jarno Argillander
  • 5,885
  • 2
  • 31
  • 33
  • You're invoking undefined behavior because the first call to `call_target` occurs on an unitialized pointer (supposing you left the rest of the code in `main` as-is). – Matthieu M. Nov 17 '11 at 12:46
0

In set_target you are passing targetChild by value, there you are loosing the chance to call TargetChild::do_something. You need to extend caller to include a reference (or pointer) to the current target, the reference (or pointer) will keep the information about the original TargetChild and then the compiler will still call TargetChild::do_something.

Werner Henze
  • 16,404
  • 12
  • 44
  • 69
0

Use an interface, ITarget and switch them at will ITarget

virtual void do_something()     { log_message("NO!"); } 

Treat everything as Type of ITarget

This a neat way of handling it.

My c++ is rusty :)

user182630
  • 574
  • 3
  • 10