How to make boost::make_shared a friend of my class
Asked Answered
A

6

9

I have written a class with protected constructor, so that new instances can only be produced with a static create() function which returns shared_ptr's to my class. To provide efficient allocation I'd like to use boost::make_shared inside the create function, however the compiler complains that my class constructor is protected inside boost::make_shared. I decided to my boost::make_shared a friend of my class but I'm puzzled about the syntax. I tried

template< class T, class A1, class A2 >
friend boost::shared_ptr<Connection> boost::make_shared(const ConnectionManagerPtr&, const std::string&);

but the compiler gived me syntax errors. Please help.

Attic answered 31/7, 2010 at 14:31 Comment(1)
As of September 2020 and Boost 1.74, there still is no supported and safe way to grant friendship to boost::make_shared.Cytochrome
L
9

You don't need to template the friend part, but you need to signify that the friend function is a template:

friend boost::shared_ptr<Connection> boost::make_shared<>(/* ... */);
//                                                     ^^

That works with Comeau and current GCC versions but fails with VC. Better would be the following form:

friend boost::shared_ptr<Connection> boost::make_shared<Connection>(/* ... */);

That works across multiple compilers now - i tested it on VC8, VC10, GCC 4.2, GCC 4.5 and Comeau 4.3.

Alternatively using a qualified name to refer to a particular instance of the function template as Martin does should work and does with Comeau, but GCC chokes on it.

A useful alternative that doesn't depend on the implementation details of make_shared() (and thus also works with VC10s TR1 implementation) is to use the pass-key-idiom for access-protection of the constructor and to befriend the create() function instead, e.g.:

class Connection {
// ...
public:
    class Key {
        friend boost::shared_ptr<Connection> create(const ConnectionManagerPtr&, 
                                                    const std::string&);
        Key() {}
    };
    Connection(const ConnectionManagerPtr&, const std::string&, const Key&);
};

boost::shared_ptr<Connection> create(const ConnectionManagerPtr& p, 
                                     const std::string& s) 
{
    return boost::make_shared<Connection>(p, s, Connection::Key());
}
Liriodendron answered 31/7, 2010 at 18:25 Comment(4)
I've just read Herb Sutter post on the subject and the outcome is that there is no portable solution to this problem. The code snippet you gave works on almost all compilers except GCC which I happen to develop on. So instead of using make_shared I reverted to using normal shared_ptr constructor.Attic
Could you please post a link to that post?Immix
@kyk: What versions? I tested that on GCC... Note that Sutters article is 7 years old.Liriodendron
Ok, the problem I was having was that I skipped constructor parameter type in friend declaration. So it shold be something like friend boost::shared_ptr<Connection> boost::make_shared<Connection, std::string>(const std::string&);Attic
V
2

I would try without the template part. After all, you want a specific instantiation of the (template) function to be a friend of your class, aren't you? Does

friend boost::shared_ptr<Connection> boost::make_shared(const ConnectionManagerPtr&, const std::string&);

work?

If that's not the solution, it might be helpful to give us the compiler messages you're getting ...

Vadavaden answered 31/7, 2010 at 14:40 Comment(3)
Unless the specification boost::make_shared says this will work, it might work sometimes, then break when the implementation of boost::make_shared is changed. You cannot know. You should never declare friend some code that you don't control.Deathlike
I cannot see how a future change in the implementation of boost::make_shared could break anything. After all declaring it as a friend has the same effect to make_shared as making all members of your class public, which is perfectly covered by the specification of make_shared. Afaik, friend will never change runtime behaviour it only influences the visibility at compile time. I agree that this is probably not good style and should perhaps be avoided, but imo this is not a reason for a downvote :) (after all, the accepted answer proposes mainly the same solution...)Vadavaden
"After all declaring it as a friend has the same effect to make_shared as making all members of your class public" Absolutely not: it makes them accessible to the definition of boost::make_shared, and you don't know what the definition of boost::make_shared contains (and you shouldn't care). Anyway, if a specification says something must be public, than it must be public, end of story.Deathlike
I
2

I think that is not the right place to use make_shared. Just construct your object with operator new and pass the pointer to shared_ptr constructor. That way you don't need to be friends with anyone.

BTW, why template arguments and function arguments are of different type?

Immix answered 31/7, 2010 at 15:3 Comment(1)
As there is no portable solution to the problem I choose this answer as accepted. BTW make_shared is preffered for performance reasons, because it requires one memory allocation per shared_ptr and gives better cache locality.Attic
T
1

I ended up using the below simple solution to enforce shared ownership. No friendship required.

class probe {
    probe() = default;
    probe(...) { ... }

    // Part I of III, private
    struct creation_token {};
    probe(probe const&) = delete;
    probe& operator=(probe const&) = delete;

public:
    // Part II of III, public
    template <class... Args>
    probe(creation_token&&, Args&&... args):
        probe(std::forward<Args>(args)...) {}

    // Part III of III, public
    template <class... Args>
    static auto create(Args&&... args) {
        return make_shared<probe>(creation_token(),
            std::forward<Args>(args)...);
    }
};
Tymothy answered 28/1, 2016 at 11:55 Comment(0)
U
-1

Below are some macros I wrote up to do this for you. In your case, you would use:

BOOST_MAKE_SHARED_2ARG_CONSTRUCTOR(Connection, const ConnectionManagerPtr&, const std::string&);

Macro definitions:

// Required includes
#include <boost/make_shared.hpp>
#include <boost/type_traits/add_reference.hpp>
#include <boost/type_traits/add_const.hpp> 

// Helper macro
#define CONST_REFERENCE(T) boost::add_reference<boost::add_const<T>::type>::type

/** BOOST_MAKE_SHARED_nARG_CONSTRUCTOR(CLASS_NAME, ARG1_TYPE, ARG2_TYPE, ...) 
  *
  * Use this macro inside the body of a class to declare that boost::make_shared
  * should be considered a friend function when used in conjunction with the
  * constructor that takes the given argument types.  This allows the constructor 
  * to be declared private (making it impossible to accidentally create an instance 
  * of the object without immediatly storing it in a boost::shared_ptr).  
  * Example usage:
  *
  * class Foo {
  *   private:
  *     Foo(int size, const char* name);
  *     MAKE_SHARED_2ARG_CONSTRUCTOR(Foo, int, const char*);
  * };
  * 
  * boost::shared_ptr<Foo> myFoo = boost::make_shared<Foo>(3, "Bob");
  *
  * Note that you need to explicitly specify the number of arguments 
  * that the constructor takes as part of the macro name.  Also, note that 
  * macros don't mix well with templated types that contain commas -- so 
  * if you have such a type, then you should typedef it to a shorter name 
  * before using it with this macro.
  */
#define BOOST_MAKE_SHARED_0ARG_CONSTRUCTOR(CLASS_NAME) \
    friend boost::shared_ptr<CLASS_NAME> boost::make_shared<CLASS_NAME>()
#define BOOST_MAKE_SHARED_1ARG_CONSTRUCTOR(CLASS_NAME, ARG_TYPE1) \
    friend boost::shared_ptr<CLASS_NAME> boost::make_shared<CLASS_NAME>(CONST_REFERENCE(ARG_TYPE1))
#define BOOST_MAKE_SHARED_2ARG_CONSTRUCTOR(CLASS_NAME, ARG_TYPE1, ARG_TYPE2) \
    friend boost::shared_ptr<CLASS_NAME> boost::make_shared<CLASS_NAME>(CONST_REFERENCE(ARG_TYPE1), CONST_REFERENCE(ARG_TYPE2))
#define BOOST_MAKE_SHARED_3ARG_CONSTRUCTOR(CLASS_NAME, ARG_TYPE1, ARG_TYPE2, ARG_TYPE3) \
    friend boost::shared_ptr<CLASS_NAME> boost::make_shared<CLASS_NAME>(CONST_REFERENCE(ARG_TYPE1), CONST_REFERENCE(ARG_TYPE2), CONST_REFERENCE(ARG_TYPE3))
#define BOOST_MAKE_SHARED_4ARG_CONSTRUCTOR(CLASS_NAME, ARG_TYPE1, ARG_TYPE2, ARG_TYPE3, ARG_TYPE4) \
    friend boost::shared_ptr<CLASS_NAME> boost::make_shared<CLASS_NAME>(CONST_REFERENCE(ARG_TYPE1), CONST_REFERENCE(ARG_TYPE2), CONST_REFERENCE(ARG_TYPE3), CONST_REFERENCE(ARG_TYPE4))
#define BOOST_MAKE_SHARED_5ARG_CONSTRUCTOR(CLASS_NAME, ARG_TYPE1, ARG_TYPE2, ARG_TYPE3, ARG_TYPE4, ARG_TYPE5) \
    friend boost::shared_ptr<CLASS_NAME> boost::make_shared<CLASS_NAME>(CONST_REFERENCE(ARG_TYPE1), CONST_REFERENCE(ARG_TYPE2), CONST_REFERENCE(ARG_TYPE3), CONST_REFERENCE(ARG_TYPE4), CONST_REFERENCE(ARG_TYPE5))
#define BOOST_MAKE_SHARED_6ARG_CONSTRUCTOR(CLASS_NAME, ARG_TYPE1, ARG_TYPE2, ARG_TYPE3, ARG_TYPE4, ARG_TYPE5, ARG_TYPE6) \
    friend boost::shared_ptr<CLASS_NAME> boost::make_shared<CLASS_NAME>(CONST_REFERENCE(ARG_TYPE1), CONST_REFERENCE(ARG_TYPE2), CONST_REFERENCE(ARG_TYPE3), CONST_REFERENCE(ARG_TYPE4), CONST_REFERENCE(ARG_TYPE5), CONST_REFERENCE(ARG_TYPE6))
#define BOOST_MAKE_SHARED_7ARG_CONSTRUCTOR(CLASS_NAME, ARG_TYPE1, ARG_TYPE2, ARG_TYPE3, ARG_TYPE4, ARG_TYPE5, ARG_TYPE6, ARG_TYPE7) \
    friend boost::shared_ptr<CLASS_NAME> boost::make_shared<CLASS_NAME>(CONST_REFERENCE(ARG_TYPE1), CONST_REFERENCE(ARG_TYPE2), CONST_REFERENCE(ARG_TYPE3), CONST_REFERENCE(ARG_TYPE4), CONST_REFERENCE(ARG_TYPE5), CONST_REFERENCE(ARG_TYPE6)), CONST_REFERENCE(ARG_TYPE7))
#define BOOST_MAKE_SHARED_8ARG_CONSTRUCTOR(CLASS_NAME, ARG_TYPE1, ARG_TYPE2, ARG_TYPE3, ARG_TYPE4, ARG_TYPE5, ARG_TYPE6, ARG_TYPE7, ARG_TYPE8) \
    friend boost::shared_ptr<CLASS_NAME> boost::make_shared<CLASS_NAME>(CONST_REFERENCE(ARG_TYPE1), CONST_REFERENCE(ARG_TYPE2), CONST_REFERENCE(ARG_TYPE3), CONST_REFERENCE(ARG_TYPE4), CONST_REFERENCE(ARG_TYPE5), CONST_REFERENCE(ARG_TYPE6)), CONST_REFERENCE(ARG_TYPE7)), CONST_REFERENCE(ARG_TYPE8))
#define BOOST_MAKE_SHARED_9ARG_CONSTRUCTOR(CLASS_NAME, ARG_TYPE1, ARG_TYPE2, ARG_TYPE3, ARG_TYPE4, ARG_TYPE5, ARG_TYPE6, ARG_TYPE7, ARG_TYPE8, ARG_TYPE9) \
    friend boost::shared_ptr<CLASS_NAME> boost::make_shared<CLASS_NAME>(CONST_REFERENCE(ARG_TYPE1), CONST_REFERENCE(ARG_TYPE2), CONST_REFERENCE(ARG_TYPE3), CONST_REFERENCE(ARG_TYPE4), CONST_REFERENCE(ARG_TYPE5), CONST_REFERENCE(ARG_TYPE6)), CONST_REFERENCE(ARG_TYPE7)), CONST_REFERENCE(ARG_TYPE8)), CONST_REFERENCE(ARG_TYPE9))
Undertrick answered 29/3, 2011 at 14:51 Comment(0)
A
-2

Just a summary of how a complete version may look like:

#include <iostream>
#include <boost/make_shared.hpp>

class Foo {
  explicit Foo(int x) {
    std::cout << "Foo::Foo(" << x << ")\n";
  }
public:
  friend boost::shared_ptr<Foo> boost::make_shared<Foo, int>(const int& x);

  static boost::shared_ptr<Foo> create(int x) {
    return boost::make_shared<Foo, int>(x);
  }

  ~Foo() {
    std::cout << "Foo::~Foo()\n";
  }
};

int main(int argc, const char *argv[]) {
  Foo::create(42);
}
Attic answered 3/8, 2010 at 10:38 Comment(2)
Where is there guaranty that friend boost::shared_ptr<Foo> boost::make_shared<Foo, int>(const int& x); has any effect?Deathlike
If this still worked I might upvote it; but (A) it's broken in C++11 because int&&, and (B) downvoted anyway for boost::make_shared<Foo, int>(x). Function templates should never be called with unnecessary explicit template arguments. Just boost::make_shared<Foo>(x) would have been strictly more correct.Cytochrome

© 2022 - 2024 — McMap. All rights reserved.