右值引用的直接实例化是否定义了行为?
Is direct instantiation of an rvalue reference defined behavior?
以下代码编译干净,使用带有 -Wall 的 g++ 6.3.0。
#include <iostream>
class Base
{
public:
Base(char const* base) : base_(base) {}
void print( char const* msg ) { print( base_, msg ); }
protected:
~Base() = default;
private:
char const* base_;
virtual void print( char const*, char const* ) = 0;
};
class Drv1 : public Base
{
public:
Drv1(char const* base, int i) : Base(base) , i_(i) {}
~Drv1() { std::cout << "Drv1 dtor" << std::endl; }
private:
int i_;
void print( char const* base, char const* msg ) override
{
std::cout << base << "(" << msg << "): " << i_ << std::endl;
}
};
class Drv2 : public Base
{
public:
Drv2(char const* base, double d) : Base(base) , d_(d) {}
~Drv2() { std::cout << "Drv2 dtor" << std::endl; }
private:
double d_;
void print( char const* base, char const* msg ) override
{
std::cout << base << "(" << msg << "): " << d_ << std::endl;
}
};
void do_test( char const* base, char const* msg, bool int_type )
{
Base&& _base(int_type ? (Base&&)Drv1(base, 1) : (Base&&)Drv2(base, 2.5));
_base.print( msg );
}
int main()
{
do_test( "Test1", "int", true );
do_test( "Test2", "double", false );
return 0;
}
当运行时的输出是这样的:
Drv1 dtor
Test1(int): 1
Drv2 dtor
Test2(double): 2.5
问题:
如果派生的 class 析构函数在调用虚函数之前已被调用,如何定义这种行为?如果输出实际上只是一个幸运的意外,那么捕获这个问题的编译器选项是什么?
rvalue reference
是 do_test()
中局部变量 _base
类型的正确术语吗?通用(或转发)引用出现在模板上下文中,但此处没有模板。
通过绑定对临时对象的引用来延长其生命周期,右值引用的规则与左值引用的规则相同——除了非常量右值引用可以绑定到临时对象,而非常量左值引用不能。
该程序确实有 UB。在这个表达式中: (Base&&)Drv2(base, 2.5)
构造了一个临时对象,并为其绑定了一个引用。接下来,该引用用于初始化完整表达式 Base&& _base(int_type ? (Base&&)Drv1(base, 1) : (Base&&)Drv2(base, 2.5));
中的另一个引用。临时引用引用的临时对象的生命周期没有延长 _base
的生命周期。因此,引用悬空。稍后访问该值有未定义的行为。
只有在使用创建临时对象的表达式直接初始化引用时,才能延长临时对象的生命周期。例如:
Base&& _base(Drv2(base, 2.5));
以下代码编译干净,使用带有 -Wall 的 g++ 6.3.0。
#include <iostream>
class Base
{
public:
Base(char const* base) : base_(base) {}
void print( char const* msg ) { print( base_, msg ); }
protected:
~Base() = default;
private:
char const* base_;
virtual void print( char const*, char const* ) = 0;
};
class Drv1 : public Base
{
public:
Drv1(char const* base, int i) : Base(base) , i_(i) {}
~Drv1() { std::cout << "Drv1 dtor" << std::endl; }
private:
int i_;
void print( char const* base, char const* msg ) override
{
std::cout << base << "(" << msg << "): " << i_ << std::endl;
}
};
class Drv2 : public Base
{
public:
Drv2(char const* base, double d) : Base(base) , d_(d) {}
~Drv2() { std::cout << "Drv2 dtor" << std::endl; }
private:
double d_;
void print( char const* base, char const* msg ) override
{
std::cout << base << "(" << msg << "): " << d_ << std::endl;
}
};
void do_test( char const* base, char const* msg, bool int_type )
{
Base&& _base(int_type ? (Base&&)Drv1(base, 1) : (Base&&)Drv2(base, 2.5));
_base.print( msg );
}
int main()
{
do_test( "Test1", "int", true );
do_test( "Test2", "double", false );
return 0;
}
当运行时的输出是这样的:
Drv1 dtor
Test1(int): 1
Drv2 dtor
Test2(double): 2.5
问题:
如果派生的 class 析构函数在调用虚函数之前已被调用,如何定义这种行为?如果输出实际上只是一个幸运的意外,那么捕获这个问题的编译器选项是什么?
rvalue reference
是do_test()
中局部变量_base
类型的正确术语吗?通用(或转发)引用出现在模板上下文中,但此处没有模板。
通过绑定对临时对象的引用来延长其生命周期,右值引用的规则与左值引用的规则相同——除了非常量右值引用可以绑定到临时对象,而非常量左值引用不能。
该程序确实有 UB。在这个表达式中: (Base&&)Drv2(base, 2.5)
构造了一个临时对象,并为其绑定了一个引用。接下来,该引用用于初始化完整表达式 Base&& _base(int_type ? (Base&&)Drv1(base, 1) : (Base&&)Drv2(base, 2.5));
中的另一个引用。临时引用引用的临时对象的生命周期没有延长 _base
的生命周期。因此,引用悬空。稍后访问该值有未定义的行为。
只有在使用创建临时对象的表达式直接初始化引用时,才能延长临时对象的生命周期。例如:
Base&& _base(Drv2(base, 2.5));