0

在玩 c-strings 和 std::string 时,我遇到了一种奇怪的行为(我相信这只是对我很好奇,并且存在一个完全有效的 c++ 答案)。通常,当我将字符串传递给类的构造函数时,我会执行以下操作:

class Foo {
public:
  Foo(const std::string& bar) bar_(bar) { }
private:
  const std::string& bar_;
};

int main() {
  Foo("Baz");
  return 0;
}

到目前为止效果很好,我(也许是天真地?)从未质疑过这种方法。

然后最近我想实现一个简单的包含数据的类,当剥离到它的基本结构时,它看起来像这样:

#include <iostream>
#include <string>

class DataContainer {
public:
  DataContainer(const std::string& name, const std::string& description)
  : name_(name), description_(description) {}
  auto getName() const -> std::string { return name_; }
  auto getDescription() const -> std::string { return description_; }
private:
  const std::string& name_;
  const std::string& description_;
};

int main() {
    auto dataContainer = DataContainer{"parameterName", "parameterDescription"};
    auto name = dataContainer.getName();
    auto description = dataContainer.getDescription();

    std::cout << "name: " << name.c_str() << std::endl;
    std::cout << "description: " << description.c_str() << std::endl;
}

输出是:

name: parameterName
description:

*.c_str()在这里使用它,因为这是我在实际代码库中使用它的方式(即使用 google 测试和EXPECT_STREQ(s1, s2).

当我在主函数中删除*.c_str()时,我得到以下输出:

name: parameterName
description: tion

所以描述的原始字符串被截断,初始字符串丢失。我可以通过将类中的类型更改为:

private:
  const std::string name_;
  const std::string description_;

现在我得到了预期的输出

name: parameterName
description: parameterDescription

很好,我可以使用这个解决方案,但我想了解这里发生了什么。另外,如果我将主要功能稍微更改为

int main() {
    auto dataContainer = DataContainer{"parameterName", "parameterDescription"};
    auto name = dataContainer.getName().c_str();
    auto description = dataContainer.getDescription().c_str();

    std::cout << "name: " << name << std::endl;
    std::cout << "description: " << description << std::endl;
}

我如何在类中存储字符串并不重要DataContainer,即通过 const ref 或 value。在这两种情况下,我得到

name: parameterName
description: 

以及关于clang的警告:

<source>:19:17: warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl]
    auto name = dataContainer.getName().c_str();

所以我猜这个问题是由 *.c_str() 本身引起的?但是,我不太明白为什么我不能通过 const ref 存储两个字符串名称和描述。任何人都可以对这个问题有所了解吗?

4

3 回答 3

1

在第一个问题中,您将const std::string&引用存储为类成员,您将悬空引用存储到临时对象

当您将字符串文字传递给构造函数时,它们本身不是std::string对象,它们是const char[]数组。因此,编译器必须创建临时 std::string对象以满足构造函数的参数,然后将引用存储到这些对象。一旦构造函数退出,这些临时对象就会被销毁,将存储的引用绑定到无效内存。

您对存储对象副本std::string而不是对原始对象的引用的修复是正确的解决方案。


在第二个问题中,您调用andc_str()的返回值,这是一个类似的问题。您正在使用指向临时内存的悬空指针getName()getDescription()

这些方法按值std::string返回对象,因此编译器在调用站点创建它们的临时副本。 返回指向对象内部数据的指针,并将这些指针存储到局部变量。但是,当它们超出范围时,临时对象会被销毁,让您的变量在您有机会使用它们之前指向无效内存。c_str()std::string

您可以通过以下三种方式之一解决此问题:

  • 通过将对象的副本std::string保存到局部变量,而不是保存它们的内部数据指针。这是您的main()代码最初所做的:
auto dataContainer = DataContainer{"parameterName", "parameterDescription"};
auto name = dataContainer.getName(); // <-- auto is deduced as std::string, name is a copy...
auto description = dataContainer.getDescription(); // <-- auto is deduced as std::string, description is a copy...

std::cout << "name: " << name.c_str() << std::endl; // <-- using c_str() pointer is safe here
std::cout << "description: " << description.c_str() << std::endl; // <-- using c_str() pointer is safe here
  • 通过完全删除局部变量并在临时对象超出范围之前c_str()直接在语句中使用指针:coutstd::string
auto dataContainer = DataContainer{"parameterName", "parameterDescription"};

std::cout << "name: " << dataContainer.getName().c_str() << std::endl; // <-- getName() returns a temp copy, but c_str() is safe to use here
std::cout << "description: " << dataContainer.getDescription().c_str() << std::endl; // <-- getDescription() returns a temp copy, but c_str() is safe to use here
  • 通过让方法返回对类成员的引用std::string,而不是返回它们的副本
auto getName() const -> const std::string& { return name_; }
auto getDescription() const -> const std::string& { return description_; }
auto dataContainer = DataContainer{"parameterName", "parameterDescription"};
auto name = dataContainer.getName().c_str(); // <-- no temp is returned here
auto description = dataContainer.getDescription().c_str(); // <-- no temp is returned here

std::cout << "name: " << name << std::endl; // using c_str() pointer is safe here!
std::cout << "description: " << description << std::endl; // <-- using c_str() pointer is safe here!

在最后一种情况下,请确保std::string在使用已保存的指针之前不要修改类成员,否则指针可能会失效。

于 2021-07-23T16:29:00.310 回答
1

如前所述,发布代码中的问题源于对临时对象的悬空引用,这些临时对象要么存储为类成员,要么返回并由.c_str().

第一个修复是将实际std::string的 s 存储为成员,而不是(悬空)引用,然后编写访问器函数返回对这些的 const 引用:

#include <iostream>
#include <string>

class DataContainer {
public:
  DataContainer(std::string name, std::string description)
    : name_(std::move(name)), description_(std::move(description)) {}
  auto getName() const -> std::string const& { return name_; }
  auto getDescription() const ->  std::string const& { return description_; }
private:
  const std::string name_;
  const std::string description_;
};

int main() {
    auto dataContainer = DataContainer{"parameterName", "parameterDescription"};
    
    std::cout << "name: " << dataContainer.getName().c_str() << std::endl;
    std::cout << "description: " << dataContainer.getDescription().c_str() << std::endl;
    return 0;
}

您可以在此处看到输出符合预期(即使使用中间局部变量)。


*.c_str()在这里使用,因为这是我使用它的实际代码库

然后考虑添加几个完全返回的访问器:

//...
auto Name() const { return name_.c_str(); }
auto Description() const { return description_.c_str(); }
//...
std::cout << "name: " << dataContainer.Name() << std::endl;
std::cout << "description: " << dataContainer.Description() << std::endl;
于 2021-07-23T19:28:27.123 回答
0

正在发生以下情况:您正在返回std::string按副本(即临时)。然后c_str()将返回一个指向该临时数据的指针,该指针将在语句之后被销毁。因此发出警告。而是返回const std::string&以摆脱它。

于 2021-07-23T15:11:50.047 回答