对于Foo类,有没有一种方法可以禁止在不给它命名的情况下构造它?
例如:
Foo("hi");
并且仅在给它起一个名字的情况下才允许它,如下所示?
Foo my_foo("hi");
第一个生命周期只是语句,第二个生命周期是封闭块。在我的用例中,Foo
正在测量构造函数和析构函数之间的时间。由于我从不引用局部变量,因此我经常忘记放入局部变量,而无意中更改了生命周期。我想得到一个编译时错误。
return std::string("Foo");
)
对于Foo类,有没有一种方法可以禁止在不给它命名的情况下构造它?
例如:
Foo("hi");
并且仅在给它起一个名字的情况下才允许它,如下所示?
Foo my_foo("hi");
第一个生命周期只是语句,第二个生命周期是封闭块。在我的用例中,Foo
正在测量构造函数和析构函数之间的时间。由于我从不引用局部变量,因此我经常忘记放入局部变量,而无意中更改了生命周期。我想得到一个编译时错误。
return std::string("Foo");
)
Answers:
另一个基于宏的解决方案:
#define Foo class Foo
该语句Foo("hi");
扩展为class Foo("hi");
,格式不正确;但Foo a("hi")
扩展为class Foo a("hi")
,这是正确的。
这样做的好处是,它与现有(正确的)代码在源和二进制方面都兼容。(此说法并不完全正确-请参阅Johannes Schaub的评论及随后的讨论:“您怎么知道它与现有代码在源代码上兼容?他的朋友包括他的标头,并且具有f(){int Foo = 0;}以前可以很好地编译,现在可以重新编译!而且,定义Foo类的成员函数的每一行都将失败:void class Foo :: bar(){}“)
void f() { int Foo = 0; }
并且以前可以编译,现在可以错误编译了!此外,定义Foo类的成员函数的每一行都会失败:void class Foo::bar() {}
。
Foo("Hi")
在Foo.cpp内部
class Foo("hi");
可以进行编译。
一点小技巧
class Foo
{
public:
Foo (const char*) {}
};
void Foo (float);
int main ()
{
Foo ("hello"); // error
class Foo a("hi"); // OK
return 1;
}
Foo a("hi");
不带class
)也将是一个错误。
Foo::Foo("hi")
在C ++中是不允许的。
将构造函数设为私有,但为类提供一个create方法。
Foo::create();
上Foo const & x = Foo::create();
std::common_type<Foo>::type()
,但您会得到一个临时权限。甚至typedef Foo bar; bar()
。
std::common_type<Foo>::type()
错误地输入。Foo const & x = ...
完全忽略掉意外。
这不会导致编译器错误,但会导致运行时错误。您可以得到一个例外,而不是测量错误的时间,这也是可以接受的。
您要保护的任何构造函数都需要一个默认参数,该参数在其上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;
};
为特色f2
,f3
和return "hello"
现在始终// OK
。
Foo f = "hello"; // may throw
这足以吓到我永远不要使用此代码。
explicit
,然后不再编译此类代码。我们的目标是禁止临时工,但确实如此。如果感到害怕,可以通过将副本的源设置为副本或将构造函数设置为非临时副本来使其不被抛出。那么如果最后一个对象仍然是临时对象,则只能抛出该对象的最后一个对象。
Foo
对象也是临时对象,并且其生存期以与默认参数相同的表达式结尾,则将在默认参数Foo
dtor之前调用对象的dtor,因为前者是在后者之后创建的。
Foo(...);
并Foo foo(...);
从里面Foo
。
几年前,我为GNU C ++编译器写了一个补丁,为这种情况添加了新的警告选项。在Bugzilla项中对此进行了跟踪。
不幸的是,GCC Bugzilla是一个埋葬的地方,经过深思熟虑的包含补丁的功能建议已死。:)
这是出于希望在代码中准确捕获属于该问题的错误的动机,该错误使用本地对象作为用于锁定和解锁,测量执行时间等的小工具。
照原样,在您的实现中,您不能执行此操作,但是可以使用此规则以发挥自己的优势:
临时对象不能绑定到非常量引用
您可以将代码从类移动到采用非常量引用参数的独立函数。如果这样做,则在临时尝试绑定到非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&)’
x
是一个命名对象,因此尚不清楚我们是否真的要禁止使用它。如果您将使用的构造函数是显式的,则人们可能会本能地这样做Foo f = Foo("hello");
。我认为如果失败,他们会生气。我的解决方案最初拒绝了它(以及非常相似的案例),但出现异常/断言失败,有人投诉。
只是没有默认的构造函数,并且确实需要在每个构造函数中都引用一个实例。
#include <iostream>
using namespace std;
enum SelfRef { selfRef };
struct S
{
S( SelfRef, S const & ) {}
};
int main()
{
S a( selfRef, a );
}
S(selfRef, a);
。:/
不,恐怕这是不可能的。但是您可以通过创建宏来获得相同的效果。
#define FOO(x) Foo _foo(x)
有了这个,您可以只写FOO(x)而不是Foo my_foo(x)。
Foo();
。
class Do_not_use_this_class_directly_Only_use_it_via_the_FOO_macro;
由于主要目标是防止错误,请考虑以下事项:
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。
fun(Foo("text"));
。