10

I'm fairly new to C++ and this is the problem I have: I have two classes, Client and Host. And when everything is loaded you have the option to press two buttons, if you press button 1 Client is loaded and if you press button 2 Host is loaded.

Now both Client and Host are fairly big classes, and I don't want to put them both into the memory. So my idea was creating a Base class, and then both Client and Host should extend the base class, and then the only thing I had to do was this:

Base connection; //If button 1 is pressed: connection = Client(); //If button 2 is pressed: connection = Host(); 

Well this sounded almost too good to be true, and when I tried it I got no errors. Now comes the problem, Base has a function called A, and Client has a function called B. So the function B is unique to the class Client.

When I try to call function B I get this error: 'class Base' has no member named 'B'. How can I let C++ know that I am talking to class Client or Host instead of Base? I am also open for a whole new approach to this problem. Maybe it's just an error in my thinking process.

Thanks in advance!

8
  • 4
    Questions related to object slicing seem popular recently. Commented Jan 23, 2013 at 23:41
  • Your code probably doesn't do what you thing it is doing. Look up object slicing. Commented Jan 23, 2013 at 23:42
  • 4
    You are instantiating a Client or Host respectively. However, you assign them to a Base variable, which converts them (well, in fact it copies) to a new Base instance, so the original identity gets lost, also known as object slicing. Commented Jan 23, 2013 at 23:42
  • I've read a quick bit on Object Slicing but is there a way I can choose runtime between the two classes without having them both in the memory? Commented Jan 23, 2013 at 23:46
  • What exactly are those different functions, A and B? There may be a more fundamental behaviour which needs abstracting. Commented Jan 23, 2013 at 23:51

4 Answers 4

5

You ran into a situation which we call object slicing, which is a common problem in languages with value semantics such as C++.

Object slicing happens when you assign a value of a sub-type to a new location (your variable connection) of a super type. This introduces a new copy of the instance, but of the super type, not the sub-type, so you lose the information about the concrete class you wanted to instantiate.

To avoid this, you have multiple options.

The classical approach uses pointers:

Base * connection; connection = new YourConcreteType(); 

Then, to use this object, you have to derefrerence it using the asterisk operator (*):

(*connection).function(); connection->function(); // syntactic sugar, semantically equivalent 

Not to forget: You have to delete the object after usage:

delete connection; 

To simplify this, C++ introduces two concepts: references and smart pointers. While the former has a restriction to be only assigned once, it is the syntactically simplest one. The latter is similar to the pointer approach, but you don't have to care about deletion, so you less likely run into a memory leak situation:

std::shared_ptr<Base> connection; connection = make_shared<YourConcreteType>(); // construction via 'make_shared' // ... use as if it were just a pointer ... connection->function(); // no delete! 

There are also other "smart pointer" types, like unique_ptr, which can be used if you do not intend to pass the pointer around (if it stays in scope).

Now, you can implement the functions in both classes separately. To make use of polymorphism, this means, during runtime, either the function of the one subclass or of the other subclass is called, depending on which one was constructed, you should declare the functions in the base class as being virtual, otherwise, the function definition in Base will be called, regardless of the concrete type you have constructed.

In your case, you want to call a function which should do something different, depending on the type. While your approach was to introduce two different functions, namely A and B, you can just declare a single function, let's call it handleEvent, as a pure virtual (= abstract) function in the base class, which means "this function is to be implemented in sub classes", and define it in the two subclasses independently:

Base { .... virtual void handleEvent(...) = 0; // "= 0" means "pure" }; // Don't provide an implementation 

Client { void handleEvent(...); // override it }; // define it for Client: void Client::handleEvent(...) { ... } 

Host { void handleEvent(...); // override it }; // define it for Host: void Host::handleEvent(...) { ... } 
Sign up to request clarification or add additional context in comments.

13 Comments

unique_ptr should be the default smart pointer. It isn't clear at all that shared ownership is required.
Thank you very much for the great explanation, I think I understand what you mean. So if I get it correct I just have to create a sort of "prototype" for B and C in the Base class, which is then edited in Client and Host. After that I call the Base class as a pointer so it can be changed but since the functions have already been initialized in the Base class with the "prototype" function I only have to reference the memory. Am I correct?
@juanchopanza Well, there are some catches and I think a shared pointer is easier to use in general, especially to newbies. Of course, it is a good thing to mention that unique pointers can be used in a lot of cases (but not all, for example in a container) and that they are more efficient.
@leemes Oh yes I am definitely interested in learning about the ins and outs of C++ programming, that's why I am so happy about this discussion :)
@ThomasVersteeg Rarity on Stack Overflow, sadly. Well, then buy a book and have a lot of fun learning C++! Good luck ;)
|
2

When you do this:

Base connection; 

You create an object connection that has exactly the memory needed for type Base.

When you do this:

connection = Client(); 

you are not expanding the memory allocated or converting connection into a Client instance. Instead, you are creating a bigger object that gets "sliced" into a smaller one.

What you want to do, is use pointers (or references or smart pointers) so that what you keep is just an address of an object that might be one type or the other type. Like this:

Base *connection; ... connection = new Client(); 

The first statement creates a pointer to a Base typed object, and the second one allocates the memory for a Client typed one, initializes it and assigns its address to connection.

2 Comments

When I do what you do, and call it like this connection->A() I still get the 'class Base' has no member named 'sendInput' error!
Typically when you have derived classes from a base class, is because they share a common interface. So you might want to declare A() as a virtual function on Base, or use a dynamic cast (not advisable). dynamic_cast<Client*>(connection)->A().
1

First, this line

connection = Client(); 

is using the assignment operator to set the state of connection, a Base, from a temporary Client object. connection is still a Base object. What you can do is the following:

std::unique_ptr<Base> makeBase(someFlagType flag) { if (flag) { return std::unique_ptr<Base>(new Cient); } else { return std::unique_ptr<Base>(new Host); } } 

Then

std::unique_ptr<Base> b = makeBase(myFlag); b->someBaseMethod(); 

Concerning the casting part, I would say that if you find yourself having to cast to a child type, you should re-think the design of the classes.

4 Comments

Yes I agree on the the rethinking part, but how would you suggest I would do it then without using unnecessary memory?
@ThomasVersteeg have one branch of the code dealing with Client, and one with Host.
I'm sorry, I don't really understand what you mean with this.
@ThomasVersteeg it really depends on the details of your application. There is no "one size fits all" solution.
1

If you want to use Base still then you are going to have to do this to use those functions:

if (Button1) { dynamic_cast<Client*>(connection)->A(); } else { dynamic_cast<Host*>(connection)->B(); } 

And you will need to make connection a pointer. Base * connection.

This isn't really ideal though. You should investigate a different way to do it like in the other answers.

1 Comment

The dynamic_casts would need to have to use -> syntax (they resolve to be pointers), and you would need to deal with the case where the connection wasn't of the appropriate type (wherein dynamic_cast returns nullptr). If you're absolutely sure your types are 'Client' (when Button1 is trie) and 'Host' otherwise, you can use a static_cast<> to downcast to the appropriate type. However; this approach is generally to be avoided and usually indicates a design issue that can be fixed using the approaches mentioned above.