static_cast<> 使用返回常量引用的隐式转换运算符的类的行为

static_cast<> behaviour on class with implicit conversion operator that returns a const reference

本文关键字:转换 运算符 引用 返回 lt cast gt static 常量      更新时间:2023-10-16

我有以下类(剥离以仅包含相关部分):

#include <string>
class Text
{
private:
    std::string _text;
public:
    Text(std::string&& text) :
        _text(std::move(text))
    {
    }
    operator const std::string&() const
    {
        return _text;
    }
};

我的问题是:如果我想获得const std::string&,我可以这样做而没有任何惩罚吗:

 Text text("fred");
 auto& s = static_cast<std::string>(text);

或者这会构建一个我最终得到引用的中间std::string吗?对于这种情况,是否有标准方法?我对C++相当陌生。

不,当您调用 static_cast<std::string>(text) 时,您正在调用隐式定义的复制构造函数并创建一个临时对象。

但是,如果您要打电话

auto& s = static_cast<const std::string&>(text);

,那么您将正确地调用显式转换运算符 operator const Noisy&()

让我们尝试一下

struct Noisy {
    Noisy() { std::cout << "Default construct" << std::endl; }
    Noisy(const Noisy&) { std::cout << "Copy construct" << std::endl; }
    Noisy(Noisy&&) { std::cout << "Move construct" << std::endl; }
    Noisy& operator=(const Noisy&) { std::cout << "C-assign" << std::endl; return *this; }
    Noisy& operator=(Noisy&&) { std::cout << "M-assign" << std::endl; return *this; }
    ~Noisy() { std::cout << "Destructor" << std::endl; }
};
class Text {
public:
    Text(Noisy&& text) : _text(std::move(text)) {}
    operator const Noisy&() const { return _text; }
private:
    Noisy _text;
};

测试 1

int main() {
    Text text(Noisy{});
    const auto& s = static_cast<Noisy>(text); // Needs 'const' to bind to temporary.
}

默认构造
移动构造
破坏者
复制构造
破坏者
破坏者

测试 2

int main() {
    Text text(Noisy{});
    auto& s = static_cast<const Noisy&>(text);
}

默认构造
移动构造
破坏者
破坏者

注意:使用选项 -fno-elide-constructors 进行编译,以避免复制优化。

我相信

这里的行为,当你static_cast std::string而不是const std::string &时,是由C++11标准的这一部分决定的:

[expr.static.cast] [5.2.9.4]:

表达式e可以使用static_cast<T>(e)形式的static_cast显式转换为类型T 如果声明T t(e) ;对于某些发明的临时变量t (8.5) 来说,格式良好。这种效果 显式转换与执行声明和初始化,然后使用临时 变量作为转换的结果。表达式 e 用作 glvalue 当且仅当初始化 将其用作 glvalue 。

如果你static_cast const std::string &那么我希望不会复制。但是如果你static_cast std::string,你必须得到一个std::string值——decltype(static_cast<T>(e))应该总是与类型 T 相同。构造新std::string的唯一方法是使用 std::string copy ctor,并且由于转换运算符未标记为 explicit ,因此以这种方式从Text获取std::string是一种有效的隐式转换。

或者这会构造一个我最终得到引用的中间 std::string 吗?

它将构建一个中间std::string,您最终通过值获得该。

正是出于这个原因,这甚至不是合法的C++。如果你static_cast某件事,你就会得到确切的那件东西,而不是别的

首先,编译器在强制转换操作的中间创建一个临时变量:

int main()
{
    string temp = "foo";
    cout << &temp << endl;
    Text text(move(temp));
    auto s = static_cast<string>(text);
    cout << &s << endl;
    return 0;
}
$ main
0x7fffb9dd7530
0x7fffb9dd7540

如您所见,变量的地址已更改

其次,当您使用auto时,编译器会隐藏所有const, volatile等修饰符

void f (int i) {}
auto s = static_cast<const string&>(text);
f(s);

错误:无法将参数的"std::basic_string"转换为"int" "1"到"无效 f(int)"

因此,如果您需要const std::string&则需要将其应用于auto变量:

const auto& s = static_cast<string>(text);
f(s);

错误:无法将"const std::basic_string"转换为"int" 参数 '1' 到 'void f(int)'

如您所见,auto关键字隐藏了const标识符。