14

I have a certain design strategy where the constructor of my class is private and can only be constructed by friends of the class. Inside the friend function, I am trying to create a unique_pointer of my class using std::make_uniquebut it doesn't compile. My VC12 compiler complains

c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory(1639): error C2248: 'Spam::Spam' : cannot access private member declared in class 'Spam'

The relevant code which fails during compilation is as follows

#include <memory>
class Spam {
public:
    friend void Foo();

private:
    Spam(int mem) :mem(mem) {}
    int mem;
};
void Foo() {
    std::unique_ptr<Spam> spam = std::make_unique<Spam>(10);
}

Why am I not able to compile?

Abhijit
  • 62,056
  • 18
  • 131
  • 204

4 Answers4

19

Here is another approach I've seen used, apparently known as the passkey idiom : have the public constructor require a private access token.

class Spam {
    struct Token {};
    friend void Foo();
public:
    Spam(Token, int mem) : mem(mem) {}

private:
    int mem;
};

void Foo() {
    std::unique_ptr<Spam> spam = std::make_unique<Spam>(Spam::Token{}, 10);
}

void Bar() {
    // error: 'Spam::Token Spam::token' is private
    // std::unique_ptr<Spam> spam = std::make_unique<Spam>(Spam::Token{}, 10);
}
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • The code compiles, but does not link with an error "undefined reference to `Spam::token'" – Mathieu Dutour Sikiric Jul 03 '18 at 07:44
  • @MathieuDutourSikiric wow, and it took three years before someone noticed as well... Fixed it, thanks! – Quentin Jul 03 '18 at 08:07
  • I'm not sure you even need the `Spam::Token` preceding the `{}` – Caleth Jul 03 '18 at 08:29
  • @Caleth I wouldn't if it wasn't for `std::make_unique` which needs to forward it, and thus know its type :) – Quentin Jul 03 '18 at 08:30
  • Thanks for correction, but I wonder how it addresses the initial issue. The initial problem is that the constructor is private and make_unique is not friended with Spam. In your code the constructor is public. That certainly addresses the issue, but how are the Spam::Token helpful in this? – Mathieu Dutour Sikiric Jul 04 '18 at 09:04
  • @MathieuDutourSikiric the constructor is visible to all, but only callable by those who can provide a `Token`, that is either the friends or whoever the friends give a token to. – Quentin Jul 04 '18 at 09:06
  • Thanks! Objectively, it looks kind of crazy construction, but why not. – Mathieu Dutour Sikiric Jul 05 '18 at 11:03
  • 1
    This is called the "passkey idiom". – Piquan Mar 04 '19 at 08:38
  • 3
    @Quentin: passkey idiom is unfortunately more complicated, private key is [not enough](http://coliru.stacked-crooked.com/a/387cf00e8c517dae), key might be public with private constructor (with friend access): [Demo](http://coliru.stacked-crooked.com/a/e6c9caebb34fa870) – Jarod42 Sep 16 '20 at 14:20
  • More on what Jarod42 is pointing out: https://arne-mertz.de/2016/10/passkey-idiom/ – Hari Jun 20 '23 at 17:54
18

In your case the function make_unique is trying to create an instance of Spam and that function is not a friend. Calling a non-friend function from inside a friend function does not imbue the non-friend function with friend status.

To solve this you can write in Foo:

std::unique_ptr<Spam> spam(new Spam(10));
Benjy Kessler
  • 7,356
  • 6
  • 41
  • 69
  • Yes you are correct. Can you please add more details to the answer? – Abhijit Apr 27 '15 at 12:51
  • There's no guarantee that `make_unique` won't delegate to an internal helper, in which case your `friend` will not work (not to mention that the signature is also wrong because `make_unique` takes its arguments by forwarding reference). – T.C. Apr 27 '15 at 16:15
  • In general, this approach is susceptible to the problems `make_unique` is supposed to solve (namely the potential for memory leak) – ricab Sep 22 '17 at 22:55
5
Why am I not able to compile?

You are unable to compile because make_unique is not a friend of Spam.

An alternative solution to making make_unique a friend is to move the creation of the unique_ptr into Spam.

class Spam {
   ...
private:
   Spam(int) {}

   static unique_ptr<Spam> create( int i ) 
   { return std::unique_ptr<Spam>( new Spam(i) ); }
};

and then have Foo call that instead.

void Foo() {
    std::unique_ptr<Spam> spam = Spam::create(10);
    ...
}
Thomas
  • 4,980
  • 2
  • 15
  • 30
  • How is this any better than just using `new` directly in `Foo`? – ricab Sep 22 '17 at 22:57
  • The use of such a construction is to force the user of the API into RAII. That way it is more difficult to create a memory leak. Calling Foo will delete spam and spam will be deleted if an exception is thrown later on inside Foo. – Thomas Sep 24 '17 at 14:14
  • But you are not forcing RAII: `Foo` can still call `new` if it wants since it is a friend. And it may decide to use `unique_ptr` in any case. The point is that `make_unique` is preferable to new. If you are going to drop it, then I don't see the benefit of the added complexity in this approach. – ricab Sep 24 '17 at 14:34
1

In your example, Foo() is a friend, but it isn't the function that's creating the Spam - make_unique is internally calling new Spam itself. The simple fix is to just have Foo() actually construct the Spam directly:

void Foo() {
    std::unique_ptr<Spam> spam(new Spam(10));
}
Barry
  • 286,269
  • 29
  • 621
  • 977