如何让boost::make_shared成为我的类的友元

9

我已经写了一个带有保护构造函数的类,这样只能通过返回指向我的类的shared_ptr的静态create()函数来创建新实例。为了提供高效的分配,我想在create函数中使用boost::make_shared,但是编译器抱怨我的类构造函数是受保护的,无法在boost::make_shared中访问。所以我决定将boost::make_shared作为我的类的friend,但是我对语法感到疑惑。我尝试过

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

但编译器给了我语法错误。请帮忙。

截至2020年9月和Boost 1.74,仍然没有受支持且安全的方法来授予boost::make_shared友元。 - Quuxplusone
6个回答

9

您不需要为friend部分创建模板,但是需要指示friend函数是一个模板:

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

这个代码可以在Comeau和现有的GCC版本中运行,但无法在VC上运行。更好的做法是使用以下形式:

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

现在这个方法可以跨多个编译器使用 - 我已经在VC8、VC10、GCC 4.2、GCC 4.5和Comeau 4.3上进行了测试。

或者像Martin一样使用限定名称来引用函数模板的特定实例,应该可以工作,并且在Comeau上确实可以工作,但是GCC会出错。

一个有用的替代方案不依赖于make_shared()的实现细节(因此也适用于VC10 TR1实现),是使用pass-key-idiom来保护构造函数的访问权限,并将create()函数设为友元。例如:

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());
}

我刚刚阅读了Herb Sutter关于这个主题的帖子,结果是没有可移植的解决方案。你提供的代码片段在几乎所有编译器上都可以工作,除了我恰好开发的GCC。因此,我放弃使用make_shared,改用普通的shared_ptr构造函数。 - kyku
请问您能否发布那篇文章的链接? - Basilevs
1
@kyk:是哪个版本?我在GCC上测试过了...请注意,Sutter的文章已经有7年了。 - Georg Fritzsche
好的,我遇到的问题是在友元声明中跳过了构造函数参数类型。所以应该像这样:friend boost::shared_ptr<Connection> boost::make_shared<Connection, std::string>(const std::string&); - kyku

2

我建议您去掉template部分。毕竟,您想让一个特定的(模板)函数实例成为您类的友元,对吧?

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

工作?

如果这不是解决方案,提供您正在收到的编译器消息可能会有帮助...


4
除非boost::make_shared的规范说明可以使用,否则有时可能会起作用,但当boost::make_shared的实现更改时就会出现问题。你无法知道这一点。您不应该将某些代码声明为友元,除非您控制该代码。 - curiousguy
我无法看出boost::make_shared实现的未来更改会如何破坏任何东西。毕竟,将其声明为friend对于make_shared具有与将类的所有成员公开相同的影响,这完全符合make_shared的规范。据我所知,friend永远不会改变运行时行为,它只影响编译时的可见性。我同意这可能不是很好的风格,也许应该避免,但在我看来,这不是一个负面评价的理由 :)(毕竟,被接受的答案主要提出了相同的解决方案...) - MartinStettner
3
“毕竟,将其声明为友元对于make_shared来说与将类的所有成员都声明为public有相同的效果。” 这绝对不是这样:它使它们可以被boost::make_shared的定义所访问,而您不知道boost::make_shared的定义包含什么(您也不应该关心)。无论如何,“如果规范要求某些内容必须是公开的,那么它必须是公开的”,就这样。 - curiousguy

2

我认为这不是使用make_shared的正确场合。只需使用operator new构造您的对象,并将指针传递给shared_ptr构造函数即可。这样,您就不需要与任何人成为朋友。

顺便问一下,为什么模板参数和函数参数是不同类型的?


3
由于该问题没有便携式解决方案,我选择接受此答案。顺便提一下,出于性能原因,make_shared 更受欢迎,因为它每个 shared_ptr 需要一个内存分配,并且具有更好的高速缓存局部性。 - kyku

1
我最终采用了以下简单的解决方案来实现共享所有权。不需要友谊关系。
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)...);
    }
};

-1

以下是我编写的一些宏,可以帮助您完成此操作。在您的情况下,您将使用:

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

宏定义:

// 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))

-2

这只是一个完整版本可能看起来的简要概述:

#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);
}

在哪里保证了 friend boost::shared_ptr<Foo> boost::make_shared<Foo, int>(const int& x); 有任何效果? - curiousguy
如果这个还能用,我可能会点赞;但是 (A) 它在 C++11 中已经失效了,因为 int&&,而且 (B) 因为 boost::make_shared<Foo, int>(x) 被踩了。函数模板不应该被不必要的显式模板参数调用。只有 boost::make_shared<Foo>(x) 才是更加正确的写法。 - Quuxplusone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接