C++ std::string and NULL const char*

8

我正在使用C++编写两个大型代码库,其中一个采用"C风格",另一个采用"C++风格"。

"C风格"的代码有一些返回const char*类型的函数,而"C++风格"的代码在很多地方都有类似于

const char* somecstylefunction();
...
std::string imacppstring = somecstylefunction();

它正在从C风格代码返回的const char*构建字符串,这种方式之前可以正常工作。

然而,当C风格代码更改并开始有时返回NULL指针时,就会导致段错误。

由于周围存在大量代码,因此我希望能够以最简洁的方式解决此问题。期望的行为是,在这种情况下,imacppstring将是空字符串。是否有一种好的、巧妙的解决方案呢?

更新

这些函数返回的const char*始终是指向静态字符串的指针。它们主要用于传递关于函数中任何意外行为的信息(可能用于日志记录)。决定让这些函数在“没有报告”时返回NULL是不错的,因为这样你可以将返回值用作条件语句,即

if (somecstylefunction()) do_something;

以前,这些函数返回的是静态字符串"";

这是否是一个好主意,我不会去碰这段代码,而且这也不由我决定。

我的目的是避免追踪每个字符串初始化来添加包装函数。


我想提醒您,您在评论中提到的“维护者”可能正在违反一些非常重要的样式规则。如果没有通过指针传递进入函数中,我不知道为什么您想返回一个char指针。您能详细说明一下吗?您可能会有更大的问题。 - San Jacinto
1
有人能回答为什么std::basic_string实现不将空值类型指针视为空字符串吗? - jmucchiello
@jmucchiello,我认为因为从根本上讲,“空字符串”与指向内存地址0的指针非常不同。空字符串 "" 实际上占用了内存作为单个空字符。 - Charles Salvia
1
你有没有试着和做出返回空指针决定的人交谈,并解释这会破坏C++代码? - David Thornley
1
如果函数返回了“”,仍然可以进行简单的检查:if (*somecstylefunction()) - UncleBens
@uncle 这不是重点... 你必须返回并更新代码。 - San Jacinto
7个回答

14

最好的做法可能是将C库函数修复为之前的行为,但您可能无法控制该库。

第二件要考虑的事情是更改所有依赖于C库函数返回空字符串的实例,使用一个包装函数来“修复”空指针:

const char* nullToEmpty( char const* s)
{
    return (s ? s : "");
}

所以现在
std::string imacppstring = somecstylefunction();

可能看起来像这样:
std::string imacppstring( nullToEmpty( somecstylefunction());

如果这种方法不可行(可能需要大量的繁琐工作,但应该只需进行一次机械性的更改),您可以实现一个“并行”库,该库具有与您当前使用的C库相同的名称,其中这些函数仅调用原始的C库函数并适当地修复NULL指针。您需要在头文件、链接器和/或C++命名空间中玩一些棘手的游戏,以使其正常工作,但这可能会在未来引起混乱,因此在采取这种方法之前应该认真考虑。
但是,以下内容可能会帮助您入门:
// .h file for a C++ wrapper for the C Lib
namespace clib_fixer {
    const char* somecstylefunction();
}


// .cpp file for a C++ wrapper for the C Lib
namespace clib_fixer {
    const char* somecstylefunction() {
        const char* p = ::somecstylefunction();

        return (p ? p : "");
    }
}

现在,您只需要将该标头添加到当前调用C lib函数的.cpp文件中(并可能删除C lib的标头),然后添加一个


using namespace clib_fixer;

使用这些函数将其添加到 .cpp 文件中。

也许这并不是太糟糕。也许。


“并且可能需要删除C库的头文件” - 我非常确定您必须将其删除。否则,对 somecstylefunction 的调用将在 namespace clib_fixernamespace :: 之间产生歧义。 - Steve Jessop

6

如果不改变每个直接从C函数调用初始化的C++ std::string的地方(添加空指针检查),唯一的解决方案就是禁止C函数返回空指针。

在GCC编译器中,您可以使用一个编译器扩展“省略操作数的条件语句”来为您的C函数创建包装宏。

#define somecstylefunction() (somecstylefunction() ? : "")

但在一般情况下,我建议不要这样做。

是的,看起来这是我想要避开的东西。修复问题的根源可能比治疗症状更好。 - San Jacinto
一些宏或者是你自己编写的库包装器可能是解决问题的好方法,而不需要改变原始库。 - Anton
1
@antonmarkov:是的,但我认为宏技巧和编译器扩展的组合有点过于复杂了。 - AnT stands with Russia

3

我想你可以添加一个封装函数来测试NULL,并返回一个空的std::string。但更重要的是,为什么您的C函数现在返回NULL? NULL指针表示什么? 如果它表示严重错误,您可能希望您的封装函数抛出异常。

或者为了安全起见,您可以先检查NULL,处理NULL情况,然后才构造std::string。

const char* s = somecstylefunction();
if (!s) explode();
std::string str(s);

在这种情况下,它并不表示错误。维护者决定使用空指针来表示“没有消息”,而不是指向静态“”的指针,这样会更加简洁。这并不是不合理的,但会破坏其他代码。 - pythonic metaphor
在这种情况下,您可以简单地使用一个内联包装函数来测试NULL,并在指针为NULL时返回一个空的std :: string。 - Charles Salvia

2
您可以将所有对C风格函数的调用包装在类似于以下内容的东西中...
std::string makeCppString(const char* cStr)
{
    return cStr ? std::string(cStr) : std::string("");
}

那么无论你在哪里有:

std::string imacppstring = somecstylefunction(); 

请将其替换为:

std::string imacppstring = makeCppString( somecystylefunction() );

当您的函数返回NULL时,这意味着构造一个空字符串是可以接受的行为。

std::string imacppstring = makeCppString(somecystylefunction); 不起作用,因为您传递的是函数的地址,而不是其返回的 char const * - underscore_d

2

对于一个便携式解决方案:

(a) 定义自己的字符串类型。最大的部分是在整个项目中进行搜索和替换 - 如果它始终是std :: string,那么可以很简单,否则就是一次大的痛苦。 (我会让唯一的要求是它可以替换为std :: string,并且还可以从null char *构造空字符串。

最简单的实现方法是公开继承std :: string。尽管这是不被推荐的(出于可以理解的原因),但在这种情况下,这样做也可以,并且有助于第三方库期望std :: string 以及调试工具。或者聚合并转发 - yuck。

(b) #define std :: string为您自己的字符串类型。风险高,不建议使用。除非我非常了解所涉及的代码库并且可以为您节省大量工作(并且我会添加一些免责声明以保护我的声誉剩余部分;)

(c) 我通过重新# define攻击性类型来解决了一些这样的情况,仅用于包含的某些实用程序类(因此#define在范围上更加有限)。但是,我不知道如何对char *进行操作。

(d) 编写导入包装器。如果C库标头具有相当规则的布局,和/或您认识一些有经验的解析C ++代码的人,您可能能够生成“包装器标头”。

(e) 请求库所有者至少在编译时使“Null字符串”值可配置。 (这是一个可以接受的请求,因为在其他情况下切换为0也可能破坏兼容性)如果这对您来说更省事,您甚至可以提供提交更改!


1

我通常不建议对标准容器进行子类化,但在这种情况下可能会起作用。

class mystring : public std::string
{
    // ... appropriate constructors are an exercise left to the reader
    mystring & operator=(const char * right)
    {
        if (right == NULL)
        {
            clear();
        }
        else
        {
            std::string::operator=(right);  // I think this works, didn't check it...
        }
        return *this;
    }
};

@Walter,我从未说过这是一个的想法,只是一种应对困境的方法。而且它有效:http://ideone.com/2xGd1A - Mark Ransom
使用C++11,为了使std::stringmystring之间的移动语义能够正常工作(无论是哪个方向),除了构造函数之外,我需要重载哪些其他成员?难道我不需要将其转换为右值引用std::string&&吗? - Walter

0

像这样做应该可以解决你的问题。

const char *cString;
std::string imacppstring;

cString = somecstylefunction();
if (cString == NULL) {
  imacppstring = "";
} else {
  imacppstring = cString;
}

如果您愿意,您可以将错误检查逻辑放在自己的函数中。这样,您就可以在更少的地方放置此代码块。

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