1

I have some design problems, I thought one of you might have some clue to help me.

I tried to summarize my problem to this simple example :

I have two different class DerivedOne and DerivedTwo which inherit from the same Base class and share the definition of a method.
I have a set of shared_ptr pointing to client, which have one object from two different class DerivedOne and DerivedTwo.
Because I don't want to use a Base* pointer to hold this 2 different class, I tried to make the client class template.

But I have 2 different class, and I can't hold them in the same set.
I thought shared_ptr could hold an object pointer without specifying the template argument, but I was wrong, or I don't know how to do.

The other solution I see, is to separate those 2 different client in 2 different set

Thanks in advance for any suggestions.

Here is the code :

#include <iostream>
#include <set>
#include <boost/shared_ptr.hpp>

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

class Derived_one
    : public Base
{
    public:
        void test() {
            std::cout << "Derived One" << std::endl;
        }            
};

class Derived_two
    : public Base
{
    public:
        void test() {
            std::cout << "Derived Two" << std::endl;
        }
};

template <class temp_arg>
class Client
{
    public:        
        int test(){
            obj_.test();
        }

    protected:
        temp_arg obj_;

};

typedef Client<Derived_one> ClientOne;
typedef Client<Derived_two> ClientTwo;    

/// Here I don't want to specify any of the two previously declared client :
//typedef boost::shared_ptr<Client> Client_ptr;
typedef boost::shared_ptr<ClientOne> Client_ptr;

int main(int, const char**)
{
    std::set<Client_ptr> Clients_;
    Client_ptr client_(new ClientOne());

    Clients_.insert(client_);
    client_->test();

    return 0;
}

If you want to give a look to the real code :
https://github.com/gravitezero/Node/tree/experimental/src
Client correspond to connection
The Base class is message
The two derived class are peply and request.

etnbrd
  • 471
  • 6
  • 18
  • Why do you don't want to use Base pointer, which could point to both the derived class objects? – Alok Save Oct 04 '11 at 16:22
  • I don't want to use pointer for some reasons. One is I don't want `dynamic_cast`, and this pointer might leed to `dynamic_cast`. Also, I want a composition and not an aggregation between client and derived objects. – etnbrd Oct 04 '11 at 16:35
  • @EtienneBrodu If you require a `dynamic_cast` something is wrong with your design in another place but not in the one you show us. A pointer is a very reasonable decision here. – pmr Oct 04 '11 at 16:42

2 Answers2

0

Why don't you want to use a pointer to a Base instance? If you use runtime-polymorphism this is the only way. If you actually don't need the runtime-polymorphism, virtual member functions are the wrong way to go.

Just use a boost::scoped_ptr or (better) a C++11 std::unique_ptr to keep a base* in Client.

If you choose to abandon the runtime-polymorphism you should look into CRTP to implement static polymorphism.

pmr
  • 58,701
  • 10
  • 113
  • 156
  • In fact, some of the members of the two derived classes are common, so I used this Base class, and I also made this method virtual, but it's not really needed. I changed this design so many times, I didn't figured out it's not needed anymore. I don't want runtime-polymorphisme, because I think I can handle this problem at compilation time. But I might be wring. I never heard about CRTP, I'm going to get some documentation, thanks :) – etnbrd Oct 04 '11 at 16:34
  • @EtienneBrodu, CRTP isn't going to help because it relies on different template instantiations, which you've already discovered is a problem. But here's an introduction anyway: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern – Mark Ransom Oct 04 '11 at 17:09
  • @MarkRansom But he would get an interface like hierarchy and can simply keep the member by value and have the Client as a template. That would solve the problem as far as I see, especially since he pointed out, that runtime operations are not required. – pmr Oct 04 '11 at 17:39
  • I posted a small CRTP example in [How to simulate virtuality for method template](http://stackoverflow.com/questions/7610350/how-to-simulate-virtuality-for-method-template/7610678#7610678) the other day – Alan Oct 04 '11 at 22:02
  • @pmr, I read some documentation about CRTP, and the case I found the most relevant for my problem, is [this answer](http://stackoverflow.com/questions/262254/c-crtp-to-avoid-dynamic-polymorphism/262984#262984). Can you tell me if I am right ? – etnbrd Oct 04 '11 at 23:45
  • @EtienneBrodu I do think so. The example is very straight forward. By the way: Make sure to associate yourself with the SO faq as well as voting and accepting answers. Makes life here a lot easier. – pmr Oct 04 '11 at 23:50
  • @pmr, I still don't understand how I can get an interface. At least, how can I get an unified interface. I will be able to have class derived from `Base` and `Base`. But if I want to pass one of them to the `client`, it will have two different client with different templates arguments. How can I get a unique `client` class that hold two different implementation of Base specified at construction ? – etnbrd Oct 05 '11 at 09:45
  • @EtienneBrodu Make client a template. – pmr Oct 05 '11 at 10:42
  • @pmr, I'm sorry, but I still don't understand. If I make `client` a template, I will not be able to store two `client` with two different template argument in the same set, or in the same variable. – etnbrd Oct 05 '11 at 11:18
  • @EtienneBrodu If this is your goal, go with the freaking pointer already. – pmr Oct 05 '11 at 11:30
0

The boost shared template pointers wont perform any magic that you can't do without regular pointers (namely putting two different types into the same collection). Whenever using a template you must explicitly declare the template arguments, because alls a template does is generate duplicate code for you at compile time. So when you use a template, the compiler simply duplicates the template file as many times as you have declared it with the template arguments. No template arguments, no duplication, no template. At least this is my understanding, I'm sure the C++ Police will correct me if I'm wrong.

So the way to put two different types into the same collection is to mask the value being placed in the collection. The most basic way of doing this (and bad way) is to make your collection of void pointers and then cast on insert and extraction depending on the context. This is dangerous, because then you will be accessing memory through the pointer without checking that the pointer points to the correct amount/format of memory (this is type checking).

So the real way to do this is as pmr suggested. Make your collection a collection of base type pointers and use dynamic cast to cast between the types when inserting and extracting from the list. A dynamic cast will check at run-time to make sure that the type you are casting from and to are compatible. IE if you cast from Derived1 to Base, and then later from Base to Derived1 this is okay. If you cast from Derived1 to base, and then later from base to Derived2 on a pointer pointing to the same memory, this will fail at run-time, because Derived2 and Derived1 are not compatible.

This explanation is a little long, because I had the same design trouble a few months ago, until I did some reading.

Ian
  • 4,169
  • 3
  • 37
  • 62