如何禁止临时工

How to disallow temporaries

本文关键字:临时工 禁止 何禁止      更新时间:2023-10-16

对于类foo,是否有一种方法可以禁止构造它而不给它命名?

例如:

Foo("hi");

,只有在您给它的名字时才允许它,如以下内容吗?

Foo my_foo("hi");

第一个寿命只是语句,第二个是封闭块。在我的用例中,Foo正在测量构造函数和驱动器之间的时间。由于我从不参考局部变量,因此我经常忘记将其放入,而意外地改变了一生。我想获得编译时间错误。

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

#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(){} {}" ))

一个小hack

怎么样
class Foo
{
    public:
        Foo (const char*) {}
};
void Foo (float);

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

将构造函数私有化,但给出了类A create 方法。

这不会导致编译器错误,而是运行时错误。您没有测量错误的时间,而是会得到一个例外,这也可能是可以接受的。

您要守卫的任何构造函数都需要默认参数,以便在哪个 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();
}

不需要f2f3的情况和"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;
}; 

f2f3return "hello"的特征始终是// OK

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

不幸的是,GCC Bugzilla是一个墓地,其中包含良好的补丁功能建议将死亡。:)

这是出于渴望准确地捕获该问题的错误的愿望,这些错误是代码中使用本地对象作为锁定和解锁的小工具,测量执行时间等等的。

,按照实现,您无法执行此操作,但是您可以使用此规则来发挥自己的优势:

临时对象不能绑定到非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;
}

输出

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&)’

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

#include <iostream>
using namespace std;
enum SelfRef { selfRef };
struct S
{
    S( SelfRef, S const & ) {}
};
int main()
{
    S a( selfRef, a );
}

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

#define FOO(x) Foo _foo(x)

使用此处,您只需编写foo(x)而不是foo my_foo(x)。

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

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。冗长但安全。

将单参数构造函数称为明确的,没有人会无意间创建该类别的对象。

例如

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。