开发者

Polymorphic class member variable

I have a class messenger which relies on a printer instance. printer is a polymorphic base class and the actual object is passed to the messenger in the constructor.

For a non-polymorphic object, I would just do the following:

class messenger {
public:
    messenger(printer const& pp) : pp(pp) { }

    void signal(std::string const& msg) {
        pp.write(msg);
    }

private:
    printer pp;
};

But when printer is a polymorphic base class,开发者_开发技巧 this no longer works (slicing).

What is the best way to make this work, considering that

  1. I don’t want to pass a pointer to the constructor, and
  2. The printer class shouldn’t need a virtual clone method (= needs to rely on copy construction).

I don’t want to pass a pointer to the constructor because the rest of the API is working with real objects, not pointers and it would be confusing / inconsistent to have a pointer as an argument here.

Under C++0x, I could perhaps use a unique_ptr, together with a template constructor:

struct printer {
    virtual void write(std::string const&) const = 0;
    virtual ~printer() { } // Not actually necessary …
};

struct console_printer : public printer {
    void write(std::string const& msg) const {
        std::cout << msg << std::endl;
    }
};

class messenger {
public:
    template <typename TPrinter>
    messenger(TPrinter const& pp) : pp(new TPrinter(pp)) { }

    void signal(std::string const& msg) {
        pp->write(msg);
    }

private:
    std::unique_ptr<printer> pp;
};

int main() {
    messenger m((console_printer())); // Extra parens to prevent MVP.

    m.signal("Hello");
}

Is this the best alternative? If so, what would be the best way in pre-0x? And is there any way to get rid of the completely unnecessary copy in the constructor? Unfortunately, moving the temporary doesn’t work here (right?).


There is no way to clone polymorphic object without a virtual clone method. So you can either:

  • pass and hold a reference and ensure the printer is not destroyed before the messenger in the code constructing messenger,
  • pass and hold a smart pointer and create the printer instance with new,
  • pass a reference and create printer instance on the heap using clone method or
  • pass a reference to actual type to a template and create instance with new while you still know the type.

The last is what you suggest with C++0x std::unique_ptr, but in this case C++03 std::auto_ptr would do you exactly the same service (i.e. you don't need to move it and they are otherwise the same).

Edit: Ok, um, one more way:

  • Make the printer itself a smart pointer to the actual implementation. Than it's copyable and polymorphic at the same time at the cost of some complexity.


Expanding the comment into a proper answer...

The primary concern here is ownership. From you code, it is appears that each instance of messenger owns its own instance of printer - but infact you are passing in a pre-constructed printer (presumably with some additional state), which you need to then copy into your own instance of printer. Given the implied nature of the object printer (i.e. to print something), I would argue that the thing to which is it is printing is a shared resource - in that light, it makes no sense for each messenger instance to have it's own copy of printer (for example, what if you need to lock to access to std::cout)?

From a design point of view, what messenger needs on construction is actually really a pointer to some shared resource - in that light, a shared_ptr (better yet, a weak_ptr) is a better option.

Now if you don't want to use a weak_ptr, and you would rather store a reference, think about whether you can couple messenger to the type of printer, the coupling is left to the user, you don't care - of course the major drawback of this is that messenger will not be containable. NOTE: you can specify a traits (or policy) class which the messenger can be typed on and this provides the type information for printer (and can be controlled by the user).

A third alternative is if you have complete control over the set of printers, in which case hold a variant type - it's much cleaner IMHO and avoids polymorphism.

Finally, if you cannot couple, you cannot control the printers, and you want your own instance of printer (of the same type), the conversion constructor template is the way forward, however add a disable_if to prevent it being called incorrectly (i.e. as normal copy ctor).

All-in-all, I would treat the printer as a shared resource and hold a weak_ptr as frankly it allows better control of that shared resource.


Unfortunately, moving the temporary doesn’t work here (right?).

Wrong. To be, uh, blunt. This is what rvalue references are made for. A simple overload would quickly solve the problem at hand.

class messenger {
public:
    template <typename TPrinter>
    messenger(TPrinter const& pp) : pp(new TPrinter(pp)) { }
    template <typename TPrinter>
    messenger(TPrinter&& pp) : pp(new TPrinter(std::move(pp))) { }

    void signal(std::string const& msg) {
        pp->write(msg);
    }

private:
    std::unique_ptr<printer> pp;
};

The same concept will apply in C++03, but swap unique_ptr for auto_ptr and ditch the rvalue reference overload.

In addition, you could consider some sort of "dummy" constructor for C++03 if you're OK with a little dodgy interface.

class messenger {
public:
    template <typename TPrinter>
    messenger(TPrinter const& pp) : pp(new TPrinter(pp)) { }
    template<typename TPrinter> messenger(const TPrinter& ref, int dummy) 
        : pp(new TPrinter()) 
    {
    }
    void signal(std::string const& msg) {
        pp->write(msg);
    }

private:
    std::unique_ptr<printer> pp;
};

Or you could consider the same strategy that auto_ptr uses for "moving" in C++03. To be used with caution, for sure, but perfectly legal and doable. The trouble with that is that you're influencing all printer subclasses.


Why don't you want to pass a pointer or a smart pointer?

Anyway, if you're always initializing the printer member in the constructor you can just use a reference member.

private:
    printer& pp;
};

And initialize in the constructor initialization list.


When you have a golden hammer everything looks like nails

Well, my latest golden hammer is type erasure. Seriously I would not use it, but then again, I would pass a pointer and have the caller create and inject the dependency.

struct printer_iface {
   virtual void print( text const & ) = 0;
};

class printer_erasure {
   std::shared_ptr<printer_iface> printer;
public:
   template <typename PrinterT>
   printer_erasure( PrinterT p ) : printer( new PrinterT(p) ) {}

   void print( text const & t ) {
      printer->print( t );
   }
};

class messenger {
   printer_erasure printer;
public:
   messenger( printer_erasure p ) : printer(p) {}
...
};

Ok, arguably this and the solutions provided with a template are the exact same thing, with the only slight difference that the complexity of type erasure is moved outside of the class. The messenger class has its own responsibilities, and the type erasure is not one of them, it can be delegated.


How about templatizing the class messanger ?

template <typename TPrinter>
class messenger {
public:
    messenger(TPrinter const& obj) : pp(obj) { }
    static void signal(printer &pp, std::string const& msg) //<-- static
    {
        pp->write(msg);
    }
private:
    TPrinter pp;  // data type should be template
};

Note that, signal() is made static. This is to leverage the virtual ability of class printer and to avoid generating a new copy of signal(). The only effort you have to make is, call the function like,

signal(this->pp, "abc");

Suppose you have other datatypes then pp which are not related to template type, then those can be moved to a non template base class and that base can be inherited by messenger. I am not describing in much details but, I wish the point should be clearer.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜