July 19th, 2021

C++11 braced initialization made the impossible possible (and how to fix it so it stays impossible)

Suppose you have a private nested type. You might use this because you need your constructor to be public in order to work with some framework,¹ but you don’t want people to do their own make_unique; you want them to go through your factory.

class Package
{
  struct private_constructor { };

public:
  // Do not call constructor directly. Use CreatePackage instead.
  Package(int id, private_constructor);

  static Package CreatePackage(int id, int flavor)
  {
    Package package(id, private_constructor());
    ... do other stuff that gets the package ready ...
    return package;
  }
};

void bad_boy()
{
  // This doesn't work. Wrong number of parameters.
  Package package(3);

  // This doesn't work. private_constructor is a private type.
  Package package(3, Package::private_constructor());
}

But C++11 introduced braced initialization, and the bad boy can use that to construct the type without naming it.

void bad_boy_got_through()
{
  // Bad boy uses empty braces to sneak past the gate!
  Package package(3, {});
}

To prevent this, you need to give your private type an explicit constructor so it cannot be used implicitly.

class Package
{
  struct private_constructor
    { explicit private_constructor() = default; };

public:
  // Do not call constructor directly. Use CreatePackage instead.
  Package(int id, private_constructor);

  ...
};

With this change, the bad boy has been foiled.

void bad_boy_foiled()
{
  // Can't sneak in with empty braces.
  Package package(3, {});
}

From Visual C++:

error C2664: 'Package::Package(Package &&)': cannot convert argument 2 from 'initializer list' to 'Package::private_constructor'

From clang:

error: converting to 'Package::private_constructor' from initializer list would use explicit constructor 'constexpr Package::private_constructor::private_constructor()'

And the explicit constructor is inaccessible.

void bad_boy_foiled()
{
  // Can't use explicit constructor.
  Package package(3, Package::private_constructor{});
}

// error: cannot access private struct

¹ For example, std::make_unique requires that the object have a public constructor.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • switchdesktopwithfade@hotmail.com

    C# has a problem similar to this and I hate it. Every C# struct has a default parameterless public constructor that just zeroes everything out. But I need my structs initialized a certain way. If I accidentally call new() instead of a factory method in the app code then I’lI have bugs, but I can’t protect against this.