c_str() 和字符串传递给 class 时的奇怪行为
Curious behaviour of c_str() and strings when passed to class
我在玩 c 字符串和 std::string 时遇到了一个奇怪的行为(我确信这对我来说只是好奇并且存在一个完全有效的 c++ 答案)。通常,当我将字符串传递给 class' 构造函数时,我会执行如下操作:
class Foo {
public:
Foo(const std::string& bar) bar_(bar) { }
private:
const std::string& bar_;
};
int main() {
Foo("Baz");
return 0;
}
到目前为止效果很好,我(也许天真?)从不质疑这种方法。
然后最近我想实现一个包含 class 的简单数据,当剥离其基本结构时,它看起来像这样:
#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
因此描述的原始字符串被截断并且缺少初始字符串。我能够通过将 class 中的类型更改为:
来解决此问题
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
class 中并不重要,即通过 const ref 或值。在这两种情况下,我得到
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 存储这两个字符串 name 和 description 。谁能阐明这个问题?
发生了以下情况:您正在 return 复制 std::string
(即临时文件)。然后 c_str()
将 return 一个指向该临时数据的指针,该指针将在语句后销毁。因此警告。 Return const std::string&
而不是摆脱它。
在第一期中,您将 const std::string&
引用存储为 class 成员,您存储 对临时对象的悬挂引用 .
当您将字符串文字传递给构造函数时,它们本身并不是 std::string
对象,而是 const char[]
数组。因此,编译器必须创建 temporary std::string
对象来满足构造函数的参数,然后您存储对这些参数的引用。一旦构造函数退出,这些临时对象就会被销毁,使您存储的引用绑定到无效内存。
您存储 std::string
对象的副本而不是对原件的引用的解决方案是正确的解决方案。
在第二期中,您在 getName()
和 getDescription()
的 return 值上调用 c_str()
,这是一个类似的问题。您正在使用 指向临时内存的悬挂指针。
这些方法是 returning std::string
对象 按值 ,因此编译器会在调用站点创建它们的临时副本。 c_str()
return 是指向 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
- 在临时
std::string
对象超出范围之前,完全删除局部变量并直接在 cout
语句中使用 c_str()
指针:
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
- 通过 return references 方法
std::string
class 成员,而不是 returning 副本 其中:
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
class 成员,否则指针可能会失效。
如前所述,已发布代码中的问题起因于对临时对象的悬垂引用,这些对象要么存储为 class 成员,要么由 .c_str()
返回和访问。
第一个修复是将实际的 std::string
存储为成员,而不是(悬挂)引用,然后编写访问器函数,返回对这些成员的常量引用:
#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;
}
你可以看到 here that the output is as expected (even when 使用中间局部变量)。
I use *.c_str()
here as this is how I use it my actual codebase
然后考虑添加几个访问器返回 exactly that:
//...
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;
我在玩 c 字符串和 std::string 时遇到了一个奇怪的行为(我确信这对我来说只是好奇并且存在一个完全有效的 c++ 答案)。通常,当我将字符串传递给 class' 构造函数时,我会执行如下操作:
class Foo {
public:
Foo(const std::string& bar) bar_(bar) { }
private:
const std::string& bar_;
};
int main() {
Foo("Baz");
return 0;
}
到目前为止效果很好,我(也许天真?)从不质疑这种方法。
然后最近我想实现一个包含 class 的简单数据,当剥离其基本结构时,它看起来像这样:
#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
因此描述的原始字符串被截断并且缺少初始字符串。我能够通过将 class 中的类型更改为:
来解决此问题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
class 中并不重要,即通过 const ref 或值。在这两种情况下,我得到
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 存储这两个字符串 name 和 description 。谁能阐明这个问题?
发生了以下情况:您正在 return 复制 std::string
(即临时文件)。然后 c_str()
将 return 一个指向该临时数据的指针,该指针将在语句后销毁。因此警告。 Return const std::string&
而不是摆脱它。
在第一期中,您将 const std::string&
引用存储为 class 成员,您存储 对临时对象的悬挂引用 .
当您将字符串文字传递给构造函数时,它们本身并不是 std::string
对象,而是 const char[]
数组。因此,编译器必须创建 temporary std::string
对象来满足构造函数的参数,然后您存储对这些参数的引用。一旦构造函数退出,这些临时对象就会被销毁,使您存储的引用绑定到无效内存。
您存储 std::string
对象的副本而不是对原件的引用的解决方案是正确的解决方案。
在第二期中,您在 getName()
和 getDescription()
的 return 值上调用 c_str()
,这是一个类似的问题。您正在使用 指向临时内存的悬挂指针。
这些方法是 returning std::string
对象 按值 ,因此编译器会在调用站点创建它们的临时副本。 c_str()
return 是指向 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
- 在临时
std::string
对象超出范围之前,完全删除局部变量并直接在cout
语句中使用c_str()
指针:
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
- 通过 return references 方法
std::string
class 成员,而不是 returning 副本 其中:
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
class 成员,否则指针可能会失效。
如前所述,已发布代码中的问题起因于对临时对象的悬垂引用,这些对象要么存储为 class 成员,要么由 .c_str()
返回和访问。
第一个修复是将实际的 std::string
存储为成员,而不是(悬挂)引用,然后编写访问器函数,返回对这些成员的常量引用:
#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;
}
你可以看到 here that the output is as expected (even when 使用中间局部变量)。
I use
*.c_str()
here as this is how I use it my actual codebase
然后考虑添加几个访问器返回 exactly that:
//...
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;