在 C++ 中将字符串文字传递给接受 const std::string & 的函数时会发生什么?

What happens when a string literal is passed to a function accepting a const std::string & in C++?

正如您可能从标题中猜到的那样,我想了解将 std::string 作为 const 引用传递给函数时到底发生了什么,因为今天早些时候我 运行 进入了一些我不太了解的情况。这是一些代码:

#include <string>
#include <stdio.h>

struct Interface {
    virtual void String1(const std::string &s) = 0;
    virtual void String2(const std::string &s) = 0;
    virtual void DoSomething() = 0;
};

struct SomeClass : public Interface {
    void String1(const std::string &s) override { s1 = s.c_str(); }
    void String2(const std::string &s) override { s2 = s.c_str(); }
    void DoSomething() override { printf("%s - %s\n", s1, s2); }

private:
    const char *s1, *s2;
};

struct AnotherClass {
    AnotherClass(Interface *interface) : interface(interface) {
        this->interface->String1("Mean string literal");
    }

    void DoTheThing() {
        std::string s("Friendlich string literal");
        interface->String2(s);
        interface->DoSomething();
    }

private:
    Interface *interface = nullptr;
};

int main(int argc, char **argv) {
    SomeClass some_class;
    AnotherClass another_class(&some_class);

    another_class.DoTheThing();
}

当在 SomeClass 中对 s1 和 s2 使用 const char * 时,程序打印 Friendlich string literal - Friendlich string literal[some rubbish] - Friendlich string literal 而不是 平均字符串文字 - Friendlich 字符串文字 正如我所期待的那样。

当为 s1 和 s2 切换到 std::string 时,它按预期工作,打印 Mean string literal - Friendlich string literal.

我和同事猜测 AnotherClass 的 ctor 中的字符串超出范围但 SomeClass 仍然有存储的字符串地址,因为 c_str()。

当对 s1 和 s2 使用 std::string 而不是 const char * 时,它实际上会生成一个副本,因此超出范围不是问题。像这样:

struct SomeClass : public Interface {
    void String1(const std::string &s) override { s1 = s; }
    void String2(const std::string &s) override { s2 = s; }
    void DoSomething() override { printf("%s - %s\n", s1.c_str(), s2.c_str()); }

private:
    std::string s1, s2;
};

所以...到底发生了什么?为什么它不能与 const char * 一起使用?为什么它适用于 std::string?

当您将字符串文字传递给接受 const std::string& 的函数时,会发生以下事件:

  • 字符串文字转换为const char*
  • 创建了一个临时 std::string 对象。它的内部缓冲区被分配,并通过从 const char* 复制数据来初始化,直到看到终止 null。参数指的是这个临时对象。
  • 函数体运行。
  • 假设函数 returns 正常,临时对象在函数 returns 和调用表达式结束之间的某个未指定点被销毁。

如果c_str()指针是从参数中保存的,由于指向临时对象的内部缓冲区,因此在销毁临时对象后成为悬垂指针。

如果函数接受std::string,也会出现类似的问题。 std::string 对象将在调用函数时创建,并在函数 returns 或之后不久销毁,因此任何保存的 c_str() 指针都将变为悬空。

如果函数接受 const std::string& 并且参数的类型为 std::string,但是,调用函数时不会创建新对象。引用引用现有对象。 c_str() 指针将保持有效,直到原始 std::string 对象被销毁。

A char * 不是对象,它是指向其他上下文中存在的字符的指针。如果将这样的指针分配给临时变量,或临时变量中包含的数据,则在临时变量被销毁时它将无效。在那之后使用它会产生未定义的行为。

当你有std::string的成员变量时,在赋值时会生成一个副本,所以临时对象是否被销毁都没有关系。