std::string::c_str()可以在任何需要字符串字面值的地方使用吗?

4
我猜测这段代码的最后两行应该可以编译。
#include "rapidjson/document.h"

int main(){
    using namespace rapidjson ;
    using namespace std ;

    Document doc ;
    Value obj(kObjectType) ;
    obj.AddMember("key", "value", doc.GetAllocator()) ; //this compiles fine
    obj.AddMember("key", string("value").c_str(), doc.GetAllocator()) ; //this does not compile!
}

我猜想是错误的,因为其中一行可以编译而另一行却不能。 AddMember方法有很多变体,如文档所述,但除此之外...为什么.c_str()的返回值不等于字符串字面值?
我的理解是,无论何处都可以接受字符串字面值,你都可以传递string::c_str()并且它应该可以工作。
附注:我正在使用VC++ 2010进行编译。
编辑: 缺少#include <string>不是问题。它已由document.h包含。
错误内容如下:
error C2664: 'rapidjson::GenericValue<Encoding> &rapidjson::GenericValue<Encoding>::AddMember(rapidjson::GenericValue<Encoding> &,rapidjson::GenericValue<Encoding> &,Allocator &)'
: cannot convert parameter 1 from 'const char [4]' to 'rapidjson::GenericValue<Encoding> &'
    with
    [
        Encoding=rapidjson::UTF8<>,
        Allocator=rapidjson::MemoryPoolAllocator<>
    ]
    and
    [
        Encoding=rapidjson::UTF8<>
    ]

编辑2:
请忽略在临时值上调用.c_str()的事实。这个例子只是为了显示编译错误。实际代码使用字符串变量。


编辑3:
该代码的备选版本:

string str("value") ;
obj.AddMember("key", "value", doc.GetAllocator()) ; //compiles
obj.AddMember("key", str, doc.GetAllocator()) ; // does not compile
obj.AddMember("key", str.c_str(), doc.GetAllocator()) ; // does not compile

你是否包含了<string>?另外,main()函数总是返回int类型。 - Dietmar Kühl
1
一个字符串字面量具有数组类型,而c_str()返回一个指针。可以有函数重载来区分它们(但是让它们行为显着不同可能会很不友好)。 - Alan Stokes
string("value").c_str() 的生命周期非常有限。因此,除非进行 const char* 的复制,否则可能会出现悬空指针。 - Jarod42
1
您忘记了包含编译器的错误信息。 - molbdnilo
AddMember()的文档链接指向http://rapidjson.org/structrapidjson_1_1_generic_string_ref.html,其中有一个示例解释了他们正在尝试捕捉您正在做什么。 - brian beuning
在C或C++中,“void main”从未是有效的。请不要发布带有“void main”的代码,因为这会使测试代码变得更加困难,并且会误导初学者程序员。已修复。 - Cheers and hth. - Alf
4个回答

5
std::string::c_str()方法返回一个char const*。字符串字面值的类型为char const[N],其中N是字符串中字符的数量(包括空终止符)。相应地,c_str()的结果不能在所有可以使用字符串字面值的地方使用!我会很惊讶如果你试图调用的接口需要一个char数组,但是在你的使用中它应该工作。更可能的是你需要包含<string>

你也不能将c_str传递给期望char*(不是const)的函数,无论它是数组还是非数组。而且你还要小心你要传递给的函数不会将指针存储在其他地方以备后用,这样虽然可以编译通过,但最终会出问题。 - Jason C
3
@JasonC:你也不能将字符串字面量传递给期望 char* 的函数或变量!在 C++11 中,这种特殊转换的 C 兼容性一直都被废弃,现已删除。 - Dietmar Kühl

4
即使这段代码编译通过:
obj.AddMember("key2", string("value").c_str(), doc.GetAllocator());

你无法保证它是安全的。

由std::string::c_str()返回的const char*指针在此语句结束之前有效。

如果AddMember方法存储字符串本身的副本,那就太好了。如果它存储一个指针,那么你就完蛋了。你需要了解AddMember的内部工作原理,才能推断代码正确性。

我怀疑作者已经想到了这一点,并构建了一些重载,要求你发送一个std::string对象(或等效物)或一个字符串字面量引用(template<std::size_t N> void AddMember(const char (&str)[N])

即使这不是他们所想的,他们也可能想保护你免受无意中发送无效指针的伤害。

尽管看起来有点麻烦,但这个编译时错误表明可能存在错误的程序。这是对库作者的致敬。因为编译时错误比运行时错误有用多达亿万倍。


在你能够对你的代码正确性进行推理之前,你需要了解AddMember的内部工作原理。好吧,你需要仔细阅读文档。但是小心使用这个构造是很好的建议,我也同意你对库作者的致敬。 - D Drmmr

3

从您提供的文档来看,似乎您正在尝试调用接受两个 StringRefType (以及一个 Allocator) 的 AddMember 重载函数。 StringRefTypeGenericStringRef<Ch> 的一个 typedef,它有两个重载的构造函数,每个函数接受一个参数:

template<SizeType N>
GenericStringRef(const CharType(&str)[N]) RAPIDJSON_NOEXCEPT;

explicit GenericStringRef(const CharType *str);

当您传递一个字符串字面量时,类型为const char[N],其中N是字符串的长度加1(用于空终止符)。使用第一个构造函数重载,可以将其隐式转换为GenericStringRef<Ch>。然而,std::string::c_str()返回一个const char*,不能隐式转换为GenericStringRef<Ch>,因为第二个构造函数重载被声明为explicit
编译器给出的错误信息是由于它选择了另一个更匹配的AddMember重载引起的。

请注意,编译器在引用的诊断中实际反应的参数是一个字面值或声明为char const [4]的数组,而不是c_str()调用。 - Cheers and hth. - Alf
@Cheersandhth.-Alf 编译器错误也涉及到参数1。因此,是我回答中的最后一句话。 - D Drmmr
1
好的,基本上,“AddMember”方法接受字符串字面量但不接受字符指针。 - GetFree

2

关于.c_str()返回值与字符串字面量的等价性问题:

字符串字面量是一个在编译时已知长度且以零结尾的数组。

c_str()方法返回一个指向在运行时才知道长度的以零结尾的字符数组的第一个元素的指针。

通常,一个字符串字面量表达式会被用在一个其值“衰减”为指向第一个元素的指针的上下文环境中,但在某些特殊情况下不会这样“衰减”,这些特例包括:

  • 绑定到数组引用,

  • 使用sizeof运算符,以及

  • 通过编译时连接多个字符串字面量(简单地按顺序写出它们)形成更大的字面量。

我认为这是一个详尽的列表。


你引用的错误信息:

cannot convert parameter 1 from 'const char [4]' to 'rapidjson::GenericValue &

并不匹配你提供的代码

#include "rapidjson/document.h"

int main(){
    using namespace rapidjson ;
    using namespace std ;

    Document doc ;
    Value obj(kObjectType) ;
    obj.AddMember("key1", "value", doc.GetAllocator()) ; //this compiles fine
    obj.AddMember("key2", string("value").c_str(), doc.GetAllocator()) ; //this does not compile!
}

在这段代码中,没有出现三个字符长的字符串字面量。
因此,“这可以编译”和“这不能编译”的说法并不是很可信。
您应该引用实际的错误信息和实际的代码(至少其中一个在编译时与您拥有的不同),并引用您调用的函数的文档。
此外,请注意,在引用的诊断中,编译器实际反应的实际参数是一个声明为字面量或数组的参数,而不是 c_str() 调用。

也许VC++没有计算空终止符? - GetFree
@GetFree:它必须计算。这是数组大小的一部分。 - Cheers and hth. - Alf
哦,你说得对,我刚意识到在写问题时我把“key”字符串加上了“1”和“2”。我会修复的。 - GetFree

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接