108

For a class Foo, is there a way to disallow constructing it without giving it a name?

For example:

Foo("hi");

And only allow it if you give it a name, like the following?

Foo my_foo("hi");

The lifetime of the first one is just the statement, and the second one is the enclosing block. In my use case, Foo is measuring the time between constructor and destructor. Since I never refer to the local variable, I often forget to put it in, and accidentally change the lifetime. I'd like to get a compile time error instead.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Martin C. Martin
  • 3,565
  • 3
  • 29
  • 36
  • 8
    This could also come in handy for mutex lock guards. – lucas clemente Oct 31 '12 at 14:02
  • 1
    Well, you _could_ write your own C++ compiler where it was forbidden, but strictly speaking it wouldn't be C++ then. There are also places where temporaries like that would be useful, like when returning an object from a function for example (like `return std::string("Foo");`) – Some programmer dude Oct 31 '12 at 14:02
  • 2
    Nope, you can't do this, sorry – Armen Tsirunyan Oct 31 '12 at 14:03
  • 2
    Depending on your religion this might be a case where macros could come handy (by usnig that type only ever via a macro that always creates a varaible) – PlasmaHH Oct 31 '12 at 14:04
  • Constructors aren't real member functions. They don't operate on an existing object, and thus there's no way of knowing whether the purported object is a temporary or not. There is no *ref-qualifier* for constructors. – Kerrek SB Oct 31 '12 at 14:08
  • 3
    Seems more like something I'd want my LINT tool to catch than something I'd want to syntactically prevent by a compiler hack. – Warren P Oct 31 '12 at 19:26
  • 1
    +1 for telling [WHY](http://www.youtube.com/watch?v=u4ZoJKF_VuA) –  Oct 31 '12 at 21:01
  • 1
    @KerrekSB: if ref-qualifiers were allowed on destructors then that would work. Unfortunately they aren't. – porges Nov 01 '12 at 03:43

10 Answers10

100

Another macro-based solution:

#define Foo class Foo

The statement Foo("hi"); expands to class Foo("hi");, which is ill-formed; but Foo a("hi") expands to class Foo a("hi"), which is correct.

This has the advantage that it is both source- and binary-compatible with existing (correct) code. (This claim is not entirely correct - please see Johannes Schaub's Comment and ensuing discussion below: "How can you know that it is source compatible with existing code? His friend includes his header and has void f() { int Foo = 0; } which previously compiled fine and now miscompiles! Also, every line that defines a member function of class Foo fails: void class Foo::bar() {}")

Faisal Vali
  • 32,723
  • 8
  • 42
  • 45
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • I think this is what we'll go with, although it doesn't work if Foo is in a namespace: A user who writes 'Stats::Foo a("hi");' will be in for a surprise. The other solution is a macro like FOO(a, "hi"), which is ugly, is a non-standard way to declare a local variable, etc. – Martin C. Martin Oct 31 '12 at 17:28
  • 52
    How can you know that it is source compatible with existing code? His friend includes his header and has `void f() { int Foo = 0; }` which previously compiled fine and now miscompiles! Also, every line that defines a member function of class Foo fails: `void class Foo::bar() {}`. – Johannes Schaub - litb Oct 31 '12 at 22:31
  • 21
    How can this get so many votes? Just look at the comment by @JohannesSchaub-litb and you will understand that this is a really bad solution. Because all of the definitions of member functions are invalid after this.. -1 from my side – Aamir Nov 01 '12 at 05:16
  • @Aamir one can simply undefine the macro inside foo.cpp. No problem. – JustMaximumPower Nov 01 '12 at 12:45
  • 2
    @JustMaximumPower: I hope that was sarcastic because if not, it is again a bad (read worse) workaround. Because we are back to square one after undefining it, which means that you will not get a compilation error (which the OP intended) on a similar line i.e., `Foo("Hi")` inside Foo.cpp now – Aamir Nov 01 '12 at 12:59
  • 1
    @Aamir No I'm serious. Martin C. Martin intents to use it to guard the usage of Foo not the implementation. – JustMaximumPower Nov 02 '12 at 18:12
  • I think .. forward declarations shall not in this solution. Do let me know if it works in that scenario. – AnotherDeveloper Nov 10 '12 at 14:01
  • 1
    I tried in Visual Studio 2012 and found `class Foo("hi");` is OK for compiling. – fresky Dec 07 '12 at 05:12
  • 1
    -1 for use of macros, and I would give another -1 for breaking other code – towi Jul 30 '13 at 20:48
71

How about a little hack

class Foo
{
    public:
        Foo (const char*) {}
};

void Foo (float);


int main ()
{
    Foo ("hello"); // error
    class Foo a("hi"); // OK
    return 1;
}
42

Make the constructor private but give the class a create method.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
dchhetri
  • 6,926
  • 4
  • 43
  • 56
  • 9
    -1: How does this solve the OP's problem at all? You can still write `Foo::create();` over `Foo const & x = Foo::create();` – Thomas Eding Oct 31 '12 at 21:23
  • @ThomasEding I guess you are right, it doesn't fix OP's core problem, but just forces him think and not make the mistake he is making. – dchhetri Oct 31 '12 at 22:06
  • 1
    @ThomasEding you cannot protect yourself against angry users that want to break the system. Even with @ecatmur's hack you can say `std::common_type::type()` and you get a temporary. Or even `typedef Foo bar; bar()`. – Johannes Schaub - litb Nov 02 '12 at 21:38
  • @JohannesSchaub-litb: But the big difference is whether or not it was by mistake or not. There's almost no way typing `std::common_type::type()` is by mistake. Leaving out the `Foo const & x = ...` by accident is totally believable. – Thomas Eding Nov 02 '12 at 21:43
25

This one doesn't result in a compiler error, but a runtime error. Instead of measuring a wrong time, you get an exception which may be acceptable too.

Any constructor you want to guard needs a default argument on which set(guard) is called.

struct Guard {
  Guard()
    :guardflagp()
  { }

  ~Guard() {
    assert(guardflagp && "Forgot to call guard?");
    *guardflagp = 0;
  }

  void *set(Guard const *&guardflag) {
    if(guardflagp) {
      *guardflagp = 0;
    }

    guardflagp = &guardflag;
    *guardflagp = this;
  }

private:
  Guard const **guardflagp;
};

class Foo {
public:
  Foo(const char *arg1, Guard &&g = Guard()) 
    :guard()
  { g.set(guard); }

  ~Foo() {
    assert(!guard && "A Foo object cannot be temporary!");
  }

private:
  mutable Guard const *guard;
}; 

The characteristics are:

Foo f() {
  // OK (no temporary)
  Foo f1("hello");

  // may throw (may introduce a temporary on behalf of the compiler)
  Foo f2 = "hello";

  // may throw (introduces a temporary that may be optimized away
  Foo f3 = Foo("hello");

  // OK (no temporary)
  Foo f4{"hello"};

  // OK (no temporary)
  Foo f = { "hello" };

  // always throws
  Foo("hello");

  // OK (normal copy)
  return f;

  // may throw (may introduce a temporary on behalf of the compiler)
  return "hello";

  // OK (initialized temporary lives longer than its initializers)
  return { "hello" };
}

int main() {
  // OK (it's f that created the temporary in its body)
  f();

  // OK (normal copy)
  Foo g1(f());

  // OK (normal copy)
  Foo g2 = f();
}

The case of f2, f3 and the return of "hello" may not be wanted. To prevent throwing, you can allow the source of a copy to be a temporary, by resetting the guard to now guard us instead of the source of the copy. Now you also see why we used the pointers above - it allows us to be flexible.

class Foo {
public:
  Foo(const char *arg1, Guard &&g = Guard()) 
    :guard()
  { g.set(guard); }

  Foo(Foo &&other)
    :guard(other.guard)
  {
    if(guard) {
      guard->set(guard);
    }
  }

  Foo(const Foo& other)
    :guard(other.guard)
  {
    if(guard) {
      guard->set(guard);
    }
  }

  ~Foo() {
    assert(!guard && "A Foo object cannot be temporary!");
  }

private:
  mutable Guard const *guard;
}; 

The characteristics for f2, f3 and for return "hello" are now always // OK.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • 2
    `Foo f = "hello"; // may throw` This is enough to scare me into never using this code. – Thomas Eding Nov 01 '12 at 18:21
  • 4
    @thomas, i recommend to mark the constructor `explicit` and then such code no more compiles. the goal was to fotbid the temporary, and it does. if you are scared, you can make it not throw by setting the source of a copy in the copy or move constructor to be a nontemporary. then only the final object of several copies may throw if it still ends up as a temporary. – Johannes Schaub - litb Nov 01 '12 at 22:02
  • @ThomasEding i implemented that tweak now. – Johannes Schaub - litb Nov 02 '12 at 20:22
  • 2
    My god. I'm not novice in C++ and C++11, but I can't understand how does this work. Could you please add a couple of explanations?.. – Mikhail Nov 02 '12 at 21:52
  • 7
    @Mikhail the order of destruction of temporary objects that are destroyed at the same points is the reverse order of their construction. The default argument that the caller passes is a temporary. If the `Foo` object is a temporary too, and its lifetime ends in the same expression as the default argument, then the `Foo` object's dtor will be invoked before the default argument's dtor, because the former was created after the latter. – Johannes Schaub - litb Nov 02 '12 at 22:15
  • If for some reason the lifetime of the temporary is extended or the object is not a temporary and thus lives longer (the former also happens in `const Foo& f = Foo("hello");`), it's the inverse. – Johannes Schaub - litb Nov 02 '12 at 22:17
  • 1
    @JohannesSchaub-litb Very nice trick. I really thought it is impossible to distinguish `Foo(...);` and `Foo foo(...);` from inside the `Foo`. – Mikhail Nov 02 '12 at 22:21
19

A few years ago I wrote a patch for the GNU C++ compiler which adds a new warning option for that situation. This is tracked in a Bugzilla item.

Unfortunately, GCC Bugzilla is a burial ground where well-considered patch-included feature suggestions go to die. :)

This was motivated by the desire to catch exactly the sort of bugs that are the subject of this question in code which uses local objects as gadgets for locking and unlocking, measuring execution time and so forth.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kaz
  • 55,781
  • 9
  • 100
  • 149
9

As is, with your implementation, you cannot do this, but you can use this rule to your advantage:

Temporary objects cannot be bound to non-const references

You can move the code from the class to an freestanding function which takes a non-const reference parameter. If you do so, You will get a compiler error if an temporary tries to bind to the non-const reference.

Code Sample

class Foo
{
    public:
        Foo(const char* ){}
        friend void InitMethod(Foo& obj);
};

void InitMethod(Foo& obj){}

int main()
{
    Foo myVar("InitMe");
    InitMethod(myVar);    //Works

    InitMethod("InitMe"); //Does not work  
    return 0;
}

Output

prog.cpp: In function ‘int main()’:
prog.cpp:13: error: invalid initialization of non-const reference of type ‘Foo&’ from a temporary of type ‘const char*’
prog.cpp:7: error: in passing argument 1 of ‘void InitMethod(Foo&)’
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • 1
    @didierc: Provided they provide a additional function.It's up to you to not do so.We are trying to tweak a way to achieve something not explicitly allowed by the standard, so ofcourse there will be restrictions. – Alok Save Oct 31 '12 at 14:39
  • @didierc the parameter `x` is a named object so it is not clear whether we really want to forbid it. If the constructor you would have used is explicit, may people would instinctively do `Foo f = Foo("hello");`. I think they would become angry if it failed. My solution initially rejected it (and very similar cases) with an exception/assert-failure and someone complained. – Johannes Schaub - litb Nov 02 '12 at 21:42
  • @JohannesSchaub-litb Yes, OP wants to forbid discarding the value generated by a constructor by forcing bindings. My example is wrong. – didierc Nov 03 '12 at 09:58
7

Simply don't have a default constructor, and do require a reference to an instance in every constructor.

#include <iostream>
using namespace std;

enum SelfRef { selfRef };

struct S
{
    S( SelfRef, S const & ) {}
};

int main()
{
    S a( selfRef, a );
}
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • 3
    Nice idea, but as soon as you have one variable: `S(selfRef, a);`. :/ – Xeo Oct 31 '12 at 23:16
  • 3
    @Xeo `S(SelfRef, S const& s) { assert(&s == this); }`, if a runtime error is acceptable. –  Oct 31 '12 at 23:18
6

No, I'm afraid this isn't possible. But you could get the same effect by creating a macro.

#define FOO(x) Foo _foo(x)

With this in place, you can just write FOO(x) instead of Foo my_foo(x).

amaurea
  • 4,950
  • 26
  • 35
  • 5
    I was going to upvote, but then I saw "you could create a macro". – Griwes Oct 31 '12 at 14:06
  • @lucasclemente, s/forbidden/reserved/ – Griwes Oct 31 '12 at 14:06
  • 1
    Ok, fixed the underscores. @Griwes - Don't be a fundamentalist. It is better to say "use a macro" than "this can't be done". – amaurea Oct 31 '12 at 14:10
  • 5
    Well, it can't be done. You haven't solved the problem at all, it's still perfectly legal to do `Foo();`. – Puppy Oct 31 '12 at 14:11
  • 11
    Now you are being stubborn here. Rename the Foo class something complicated, and call the macro Foo. Problem solved. – amaurea Oct 31 '12 at 14:13
  • 8
    Something like: `class Do_not_use_this_class_directly_Only_use_it_via_the_FOO_macro;` – Benjamin Lindley Oct 31 '12 at 14:21
  • You don't even need to change the class's name. Just define the macro after the class with the exact same name as the class and undefine the macro in the implementation file. Though, I think adding a macro param for the var name would be nice. That way, you can access the variable. – Thomas Eding Oct 31 '12 at 21:24
  • @BenjaminLindley When compilation errors *do* occur, it will look really weird... – Mateen Ulhaq Nov 01 '12 at 00:43
4

Since the primary goal is to prevent bugs, consider this:

struct Foo
{
  Foo( const char* ) { /* ... */ }
};

enum { Foo };

int main()
{
  struct Foo foo( "hi" ); // OK
  struct Foo( "hi" ); // fail
  Foo foo( "hi" ); // fail
  Foo( "hi" ); // fail
}

That way you can't forget to name the variable and you can't forget to write struct. Verbose, but safe.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
1

Declare one-parametric constructor as explicit and nobody will ever create an object of that class unintentionally.

For example

class Foo
{
public: 
  explicit Foo(const char*);
};

void fun(const Foo&);

can only be used this way

void g() {
  Foo a("text");
  fun(a);
}

but never this way (through a temporary on the stack)

void g() {
  fun("text");
}

See also: Alexandrescu, C++ Coding Standards, Item 40.

stefan.gal
  • 302
  • 2
  • 6