如何禁止临时

Mar*_*tin 107 c++

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

例如:

Foo("hi");
Run Code Online (Sandbox Code Playgroud)

如果你给它一个名字,只允许它,如下所示?

Foo my_foo("hi");
Run Code Online (Sandbox Code Playgroud)

第一个的生命周期只是语句,第二个是封闭块.在我的用例中,Foo是测量构造函数和析构函数之间的时间.因为我从不参考局部变量,所以我常常忘记将其放入,并且意外地改变了生命周期.我想改为编译错误.

eca*_*mur 101

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

#define Foo class Foo
Run Code Online (Sandbox Code Playgroud)

声明Foo("hi");扩大到class Foo("hi");,形成不良; 但Foo a("hi")扩展到class Foo a("hi"),这是正确的.

这样做的好处在于它既是源代码与二进制兼容现有的(正确)的代码.(这个说法并不完全正确 - 请参阅Johannes Schaub的评论以及随后的讨论:"你怎么知道它与现有代码的源代码兼容?他的朋友包括他的标题并且有void f(){int Foo = 0;}而此前编译罚款,现在miscompiles此外,每一行定义类Foo的成员函数失败:无效类Foo ::巴(){}")

  • 你怎么知道它与现有代码的源兼容?他的朋友包括他的标题并且有`void f(){int Foo = 0; 以前编译好的,现在错误编译!此外,定义类Foo的成员函数的每一行都失败:`void class Foo :: bar(){}`. (51认同)
  • 怎么能得到这么多的票呢?只要看看@ JohannesSchaub-litb的评论,您就会明白这是一个非常糟糕的解决方案.因为在此之后所有成员函数的定义都是无效的.. -1 (21认同)
  • @JustMaximumPower:我希望这是讽刺的,因为如果没有,它又是一个糟糕的(更糟糕的)解决方法.因为我们在取消定义之后回到了原点,这意味着你不会在类似的行上得到编译错误(即你想要的),即Foo.cpp里面的`Foo("Hi")` (2认同)

小智 71

怎么样一个小黑客

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

void Foo (float);


int main ()
{
    Foo ("hello"); // error
    class Foo a("hi"); // OK
    return 1;
}
Run Code Online (Sandbox Code Playgroud)

  • undu,你用的是什么编译器?gcc 3.4抱怨没有转换为浮动.它试图调用一个函数`Foo`,因为它优先于一个类. (2认同)

dch*_*tri 45

使构造函数为私有,但为类提供create方法.

  • +1这种方法是我见过的唯一非黑客. (9认同)
  • -1:这如何解决OP的问题?你仍然可以在`Foo const&x = Foo :: create();`上写`Foo :: create();` (9认同)

Joh*_*itb 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;
}; 
Run Code Online (Sandbox Code Playgroud)

特点是:

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();
}
Run Code Online (Sandbox Code Playgroud)

的情况下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;
}; 
Run Code Online (Sandbox Code Playgroud)

为特色f2,f3return "hello"现在始终// OK.

  • @Mikhail破坏在同一点被破坏的临时对象的顺序与它们构造的顺序相反.调用者传递的默认参数是临时的.如果`Foo`对象也是临时对象,并且它的生命周期与默认参数的表达式相同,那么将在默认参数的dtor之前调用`Foo`对象的dtor,因为前者是在后者之后创建的. (6认同)
  • @thomas,我建议标记构造函数`explicit`,然后这样的代码不再编译.目标是拍摄临时的,它确实如此.如果你害怕,你可以通过在副本中设置副本的来源或将构造函数设置为非临时来使其不被抛出.然后只有最后一个副本的最终对象可能会抛出,如果它仍然作为临时结束. (4认同)
  • `Foo f ="你好"; //可能会抛出`这足以吓唬我从不使用这段代码. (2认同)
  • 天哪.我不是C++和C++ 11的新手,但我无法理解它是如何工作的.你能加点几个解释吗? (2认同)

Kaz*_*Kaz 18

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

不幸的是,GCC Bugzilla是一个埋葬地,其中考虑周全的补丁包含功能建议将会消失.:)

这是因为希望在代码中捕获这个问题主题的错误,这些错误使用本地对象作为锁定和解锁的小工具,测量执行时间等等.


Alo*_*ave 9

按照您的实现,您无法执行此操作,但您可以使用此规则:

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

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

代码示例

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;
}
Run Code Online (Sandbox Code Playgroud)

产量

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&)’
Run Code Online (Sandbox Code Playgroud)


Che*_*Alf 7

只是没有默认的构造函数,并且需要在每个构造函数中引用一个实例.

#include <iostream>
using namespace std;

enum SelfRef { selfRef };

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

int main()
{
    S a( selfRef, a );
}
Run Code Online (Sandbox Code Playgroud)

  • 好主意,但只要你有一个变量:`S(selfRef,a);`.:/ (3认同)
  • @Xeo`S(SelfRef,S const&s){assert(&s == this); 如果运行时错误可以接受的话. (3认同)

ama*_*rea 6

不,我担心这是不可能的.但是你可以通过创建一个宏来获得相同的效果.

#define FOO(x) Foo _foo(x)
Run Code Online (Sandbox Code Playgroud)

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

  • 现在你在这里固执.将Foo类重命名为复杂的东西,并调用宏Foo.问题解决了. (11认同)
  • 类似于:`class Do_not_use_this_class_directly_Only_use_it_via_the_FOO_macro;` (8认同)
  • 我打算投票,但后来我看到"你可以创建一个宏". (5认同)
  • 好吧,它无法完成.你根本没有解决问题,做'Foo();`仍然是完全合法的. (5认同)