5

Given an abstract interface and an implementation derived from that interface, where constructors are protected (creation of these objects only being available from a class factory - to implement a DI pattern), how can I make use of make_shared in the factory function?

For example:

class IInterface
{    
public:    
    virtual void Method() = 0;
};

class InterfaceImpl : public IInterface
{
public:
    virtual void Method() {}

protected:    
    InterfaceImpl() {}    
};

std::shared_ptr<IInterface> Create()
{
    std::shared_ptr<IInterface> object = std:: make_shared<InterfaceImpl>();    
    return object;
}

make_shared obviously cannot access the protected constructor in InterfaceImpl, or indeed in IInterface, giving me the following error


error C2248: 'InterfaceImpl::InterfaceImpl' : cannot access protected member declared in class 'InterfaceImpl'

So reading here (question: How to make boost::make_shared a friend of my class), I tried putting the following into the implementation class:


friend std::shared_ptr<InterfaceImpl> std::make_shared<InterfaceImpl>();

It still wouldn't compile. So then I put another one into the IInterface class too. Still no joy. What have I done wrong here?

EDIT: Full source file used to compile, with "friend"...

#include <memory>

class IInterface
{    
public:    
    friend std::shared_ptr&lt;IInterface> Create();     
    virtual void Method() = 0;
};

class InterfaceImpl : public IInterface
{    
public:     
    virtual void Method() {}

protected:    
    friend std::shared_ptr&lt;IInterface> Create();     
    InterfaceImpl() {}    
};

std::shared_ptr<IInterface> Create()
{
    std::shared_ptr<IInterface> object = std::make_shared<InterfaceImpl>();    
    return object;
}

void main()
{
    std::shared_ptr<IInterface> i = Create();   
}
Community
  • 1
  • 1
Robinson
  • 9,666
  • 16
  • 71
  • 115
  • I guess thats VC10? GCC btw has no problems as long as you befriend `make_shared()`. – Georg Fritzsche Aug 22 '10 at 13:48
  • It's VS2010, which actually gives a warning (erroneously - detailed here: http://connect.microsoft.com/VisualStudio/feedback/details/321690/c-vc9-bogus-warning-c4396-for-valid-code). – Robinson Aug 22 '10 at 13:49

2 Answers2

4

To the original question, std::make_shared<...>() does not directly instantiate your class, so providing friend access to it yields no benefit, as you found. You can simply provide friend access to the code that does directly use your protected constructor as follows:

friend class std::tr1::_Ref_count_obj<TheClassManagedByTheShared_Ptr>;

or in your case:

friend class std::tr1::_Ref_count_obj<InterfaceImpl>;

This works with the Microsoft compiler in VS2010, but it looks like it may be environment specific as it does not work with gcc on Linux. With gcc the std::tr1 namespace does not exist, so it must be specific to the Microsoft implementation of std library.

My normal working environment is the Intel 12.1 compiler, which appears to have a bug that does not check for access at all, and happily builds code without any friend declaration.

Tom
  • 41
  • 2
4

With VC10 the solution you linked to doesn't work - the construction of the instance of InterfaceImpl doesn't happen in make_shared, but in an internal type in std::tr1::_Ref_count_obj<Ty>::_Ref_count_obj(void).

I'd just make the Create() function a friend in your case and not use make_shared():

class InterfaceImpl : public IInterface {
// ...    
protected:
    friend std::shared_ptr<IInterface> Create();
    InterfaceImpl() {}
};

std::shared_ptr<IInterface> Create() {
    return std::shared_ptr<IInterface>(new InterfaceImpl());
}

... or use a custom make_shared() implementation that you actually can befriend without relying on ugly implementation details.

An alternative would be to use something like this pass-key-idiom:

class InterfaceImpl : public IInterface {
public:
    class Key {
        friend std::shared_ptr<IInterface> Create();
        Key() {}
    };
    InterfaceImpl(const Key&) {}
};

std::shared_ptr<IInterface> Create() {
    std::shared_ptr<IInterface> object = 
        std::make_shared<InterfaceImpl>(InterfaceImpl::Key());
    return object;
}
Community
  • 1
  • 1
Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
  • That doesn't compile either I'm afraid! – Robinson Aug 22 '10 at 14:05
  • @rob: Huh, i tested it in VC10 before posting. Are you testing with different code? – Georg Fritzsche Aug 22 '10 at 14:08
  • Maybe I missed something. Here's the code I'm compiling (follow up "answer" as the comment doesn't allow code it seems...!)... – Robinson Aug 22 '10 at 14:11
  • @rob: Don't post *"answers"* for such things, rather edit the question for updates. – Georg Fritzsche Aug 22 '10 at 14:12
  • @rob: Your update still uses `make_shared()` - try copy/pasting the `Create()` from my answer. – Georg Fritzsche Aug 22 '10 at 14:18
  • Oh ok, yes, I see. That's what I'm doing at the moment. I was hoping to use make_shared instead of the standard shared_ptr construction, not just because it's "the great new thing to do", but because it may give me a little performance boost (cache coherence). I understand the arguments about premature optimisation. I just thought if I could do it, there's no harm in doing it. Obviously I can't do it! – Robinson Aug 22 '10 at 14:22
  • @rob: Ok, actually there is a way to use `make_shared()` if performance is a concern - updated the answer. I wonder why i didn't think of that earlier. – Georg Fritzsche Aug 22 '10 at 14:31
  • Yes, that works! I'll have to spend some time trying to figure out why it works though :p. Thanks Georg. – Robinson Aug 22 '10 at 14:48