对于 Foo 类,有没有办法在不给它命名的情况下禁止构造它?
例如:
Foo("hi");
并且仅在您为其命名时才允许它,如下所示?
Foo my_foo("hi");
第一个的生命周期只是语句,第二个是封闭块。在我的用例中,Foo
测量构造函数和析构函数之间的时间。由于我从不引用局部变量,所以经常忘记放入,不小心改变了生命周期。我想得到一个编译时错误。
对于 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() {}")
来个小技巧怎么样
class Foo
{
public:
Foo (const char*) {}
};
void Foo (float);
int main ()
{
Foo ("hello"); // error
class Foo a("hi"); // OK
return 1;
}
将构造函数设为私有,但给类一个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();
}
的情况下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
。
几年前,我为 GNU C++ 编译器编写了一个补丁,为这种情况添加了一个新的警告选项。这在Bugzilla 项目中进行了跟踪。
不幸的是,GCC Bugzilla 是一个埋葬地,经过深思熟虑的包含补丁的功能建议会死去。:)
这样做的动机是希望在代码中准确捕获作为该问题主题的那种错误,该代码使用本地对象作为锁定和解锁的小工具,测量执行时间等等。
按照原样,在您的实现中,您不能这样做,但您可以使用此规则来发挥您的优势:
临时对象不能绑定到非常量引用
您可以将代码从类移动到采用非常量引用参数的独立函数。如果这样做,如果临时尝试绑定到非常量引用,您将收到编译器错误。
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 条。