我找不到对此给出明确答案的线程-
我有一个构造函数,例如:
FanBookPost::FanBookPost(Fan* owner, std::string content);
Fan 是我代码中的另一个类,但内容是有问题的类:
由于 std::string 不是指针,我希望用 (fan, nullptr) 调用构造函数是不可能的。但是,它编译!...并在运行时使用 EXC_BAD_ACCESS 崩溃。
这是可以避免的吗?
这里的问题是,当调用构造函数时会发生崩溃std::string
(作为一个 nullptr 解释为const char*
在那里访问)。在这里你无能为力,只能告诉其他人不要做坏事。这不是您的构造函数的问题,因此不是您的责任(除了您无法阻止它)。
您正在观察的是,您可以std::string
从字符指针(nullptr
在本例中)隐式创建 a,然后将其传递给函数。string
但是,不允许从空指针创建 a 。您的方法签名没有任何问题,只是违反std::string
构造函数合同的客户端使用。
问题在于它std::string
有一个非显式 ctor,它以 achar *
作为其唯一(必需)参数。这给出了从nullptr
to的隐式转换std::string
,但给出了未定义的行为,因为该 ctor 特别需要一个非空指针。
有几种方法可以防止这种情况。可能最有效的方法是对 a 进行(非常量)引用std::string
,这需要将 a (非临时)string
作为参数传递。
FanBookPost::FanBookPost(Fan* owner, std::string &content);
这确实会带来不幸的副作用,即赋予函数修改传递的字符串的能力。这也意味着(使用符合标准的编译器1 )您将无法将字符串文字传递给函数——您必须传递.nullptr
std::string
如果您希望能够传递字符串文字,则可以添加一个带char const *
参数的重载,也可能添加一个带参数的重载nullptr_t
。前者会在创建字符串并调用引用字符串的函数之前检查非空指针,后者会做一些事情,比如记录错误并无条件地终止程序(或者,可能只是记录错误并抛出异常)。
这既烦人又不方便,但可能优于目前的情况。
如果您真的想要安全,如何使用代理/包装器类型:
template<typename T>
struct e_t
{
public:
inline e_t ( e_t const & other )
: m_value( other.m_value )
{}
inline T & value( void ) { return m_value; }
inline operator T&() { return m_value; }
inline e_t( const T& c ) : m_value( c ) {}
private:
T m_value;
};
void FanBookPost(int* owner, e_t<std::string> content) {
}
int main()
{
int n = 0;
//FanBookPost(&n, 0); // compiler error
//FanBookPost(&n, nullptr); // compiler error
//FanBookPost(&n, ""); // unfortunately compiler error too
FanBookPost(&n, std::string(""));
}