108

对于 Foo 类,有没有办法在不给它命名的情况下禁止构造它?

例如:

Foo("hi");

并且仅在您为其命名时才允许它,如下所示?

Foo my_foo("hi");

第一个的生命周期只是语句,第二个是封闭块。在我的用例中,Foo测量构造函数和析构函数之间的时间。由于我从不引用局部变量,所以经常忘记放入,不小心改变了生命周期。我想得到一个编译时错误。

4

10 回答 10

100

另一个基于宏的解决方案:

#define Foo class Foo

该语句Foo("hi");扩展为class Foo("hi");,这是格式错误的;但Foo a("hi")扩展为class Foo a("hi"),这是正确的。

这样做的好处是它与现有的(正确的)代码既源代码兼容又二进制兼容。(这种说法并不完全正确 - 请参阅 Johannes Schaub 的评论和下面的讨论:“你怎么知道它与现有代码的源代码兼容?他的朋友包括他的标题并且有 void f() { int Foo = 0; }以前编译得很好,现在编译错误!此外,定义类 Foo 的成员函数的每一行都失败: void class Foo::bar() {}"

于 2012-10-31T14:32:10.430 回答
71

来个小技巧怎么样

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

void Foo (float);


int main ()
{
    Foo ("hello"); // error
    class Foo a("hi"); // OK
    return 1;
}
于 2012-10-31T14:08:15.243 回答
42

将构造函数设为私有,但给类一个create方法。

于 2012-10-31T14:33:52.720 回答
25

这不会导致编译器错误,而是运行时错误。不是测量错误的时间,而是得到一个也可以接受的异常。

您想要保护的任何构造函数都需要一个set(guard)调用的默认参数。

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

特点是:

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

的情况下f2,可能不需要f3返回。为了防止抛出,您可以通过将 重置为现在保护我们而不是"hello"副本的来源来允许副本的来源是临时的。guard现在你也明白了为什么我们使用上面的指针——它让我们变得灵活。

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

for 和 for 现在的f2特征f3始终return "hello"// OK

于 2012-10-31T23:00:49.940 回答
19

几年前,我为 GNU C++ 编译器编写了一个补丁,为这种情况添加了一个新的警告选项。这在Bugzilla 项目中进行了跟踪。

不幸的是,GCC Bugzilla 是一个埋葬地,经过深思熟虑的包含补丁的功能建议会死去。:)

这样做的动机是希望在代码中准确捕获作为该问题主题的那种错误,该代码使用本地对象作为锁定和解锁的小工具,测量执行时间等等。

于 2012-10-31T16:46:43.123 回答
9

按照原样,在您的实现中,您不能这样做,但您可以使用此规则来发挥您的优势:

临时对象不能绑定到非常量引用

您可以将代码从类移动到采用非常量引用参数的独立函数。如果这样做,如果临时尝试绑定到非常量引用,您将收到编译器错误。

代码示例

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

输出

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&)’
于 2012-10-31T14:10:22.880 回答
7

根本没有默认构造函数,并且确实需要在每个构造函数中引用一个实例。

#include <iostream>
using namespace std;

enum SelfRef { selfRef };

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

int main()
{
    S a( selfRef, a );
}
于 2012-10-31T23:01:08.080 回答
6

不,恐怕这是不可能的。但是您可以通过创建宏来获得相同的效果。

#define FOO(x) Foo _foo(x)

有了这个,你可以只写 FOO(x) 而不是 Foo my_foo(x)。

于 2012-10-31T14:04:19.860 回答
4

由于主要目标是防止错误,请考虑以下问题:

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
}

这样你就不会忘记给变量命名,也不会忘记写struct. 冗长,但安全。

于 2013-02-18T23:15:25.323 回答
1

将单参数构造函数声明为显式的,没有人会无意中创建该类的对象。

例如

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

void fun(const Foo&);

只能这样使用

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

但从来没有这样(通过堆栈上的临时)

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

另请参阅:Alexandrescu,C++ 编码标准,第 40 条。

于 2012-11-06T19:37:38.553 回答