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.

kyku
  • 5,892
  • 4
  • 43
  • 51
  • As of September 2020 and [Boost 1.74](https://www.boost.org/doc/libs/1_74_0/boost/smart_ptr/make_shared_object.hpp), there still is no supported and safe way to grant friendship to `boost::make_shared`. – Quuxplusone Sep 02 '20 at 14:31

6 Answers6

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());
}
Community
  • 1
  • 1
Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
  • 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. – kyku Aug 01 '10 at 11:07
  • Could you please post a link to that post? – Basilevs Aug 01 '10 at 14:29
  • 1
    @kyk: What versions? I tested that on GCC... Note that Sutters article is 7 years old. – Georg Fritzsche Aug 01 '10 at 23:41
  • 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 boost::make_shared(const std::string&); – kyku Aug 03 '10 at 10:42
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 ...

MartinStettner
  • 28,719
  • 15
  • 79
  • 106
  • 4
    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. – curiousguy Oct 10 '11 at 13:46
  • 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...) – MartinStettner Oct 10 '11 at 15:12
  • 3
    "_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. – curiousguy Oct 10 '11 at 15:38
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?

Basilevs
  • 22,440
  • 15
  • 57
  • 102
  • 3
    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. – kyku Aug 01 '10 at 11:09
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)...);
    }
};
bobah
  • 18,364
  • 2
  • 37
  • 70
-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))
Edward Loper
  • 15,374
  • 7
  • 43
  • 52
-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);
}
kyku
  • 5,892
  • 4
  • 43
  • 51
  • Where is there guaranty that `friend boost::shared_ptr boost::make_shared(const int& x);` has any effect? – curiousguy Oct 10 '11 at 14:11
  • 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(x)`. Function templates should never be called with unnecessary explicit template arguments. Just `boost::make_shared(x)` would have been strictly more correct. – Quuxplusone Sep 02 '20 at 14:26