使用等号运算符初始化对象

4
在以下定义为foo的类中
class foo{
private:
    string str;
public:
    foo operator = (string s){
         str = s;
    }
};

int main(){
    foo a = "this initialization throwing an error";
    foo b;
    b = "but assignment after declaration is working fine";
}

错误:请求将'const char [38]'转换为非标量类型'foo'

上述错误仅在我使用声明的对象实例赋值时发生,但如果我从声明中分别赋值,则重载的等于号=运算符可以正常工作。

我想通过任何方法使用等于号将字符串分配给对象,既要能够用等于号进行声明,也要能够分别分配值,例如 foo a = "abcd";


3
你需要一个转换构造函数,即 foo(const std::string& s) : str(s) {}。它可以将 std::string 类型的参数转换为 foo 类型的对象,并使用该参数来初始化 foo 对象的成员变量 str - Fred Larson
4
出错的代码并不是“给对象分配值……”,而是试图初始化该对象,这需要使用构造函数。 - Pete Becker
2
你需要从一个const char*构建一个构造函数。 - Bathsheba
5个回答

4

When you have

type name = something;

你并没有进行赋值操作,而是使用了拷贝初始化(请注意,即使称为拷贝,也可能会发生移动)。这意味着你的类需要一个构造函数,其参数与等号右侧的内容匹配。
在这种情况下,你没有一个接受std::stringconst char*const char[]的构造函数,因此编译器无法在那里构造一个实例。
第二种情况能够工作的原因是:
b = "but assignment after declaration is working fine";

它没有尝试构建任何东西,因此调用了赋值运算符,这是有效的,因为

"but assignment after declaration is working fine"

这段文字的意思是:可以将其转换为 std::string

如果你想让你的类能够从字符串字面量或 cstring 构造,那么可以为该类添加如下形式的构造函数:

foo(const char* str) : str(str) {}

有没有办法确保数据被视为 std::stringconst char*const char[],因为在我的情况下,我认为它是将数据作为 std::string 接受的,但现在我才发现它是作为 const char* 接受的。 - Abhay Jatin Doshi
1
@Encryptor 刚刚添加了一个构造函数,应该可以满足你的需求。 - NathanOliver
“或者需要转换为这些类型之一” 这是不正确的陈述。 - Slava
@Slava 是的。我在打字时就觉得那听起来不对劲。谢谢你指出来。 - NathanOliver
@NathanOliver,你最后的陈述有点令人困惑 :) - Slava

2
在第一种情况下,你所拥有的被称为“复制初始化”,正如文档中所述:(链接)

命名变量的复制初始化中的等号=与赋值运算符无关。 赋值运算符重载对复制初始化没有影响。

因此出现了错误。 可能的解决方案如下:
首先,你可以创建一个接受std :: string的构造函数:
foo( const std::string &s );

通过以下方式,您可以创建foo

foo f( "abc" );

甚至包括这个:

foo f = foo( "abc" );

但是你的语句仍然会失败,因为:

此外,在复制初始化中的隐式转换必须直接从初始化程序生成 T ,而直接初始化(例如)期望从初始化程序到 T 的构造函数参数的隐式转换。

所以要使您的语句按原样工作,您需要添加此构造函数:

 foo( const char *str );

注意:当您添加正确的构造函数时,您不需要定义赋值运算符 - 转换构造函数将被使用。而您重载的赋值运算符应该返回对 foo 的引用。

1
你的问题是:
foo a = "initialization string";

尝试创建一个类型为foo的对象,但是没有定义接受字符串参数的构造函数。 您可以像这样定义一个构造函数:
foo(const std::string& s) : str(s) {}
foo(const char* s) : str(s) {}

但是,然后OP的代码将不得不创建一个匿名临时std :: string。也许编译器会优化掉它? - Bathsheba
1
你也可以创建一个移动构造函数,它接受一个 std::string。如果你使用字符串字面量调用它,这将解决问题。 - Emmanuel Mathi-Amorim
很遗憾,你错了,这个构造函数不能解决 OP 的问题。 - Slava
@Slava 你是对的,那样行不通。我已经更新了帖子,添加了一个接受const char*参数的构造函数。 - Emmanuel Mathi-Amorim

1
当您创建一个对象时,即使使用=符号进行创建,代码也会使用构造函数对其进行初始化
struct S {
    S(int);
};

S s = 3; // initialization, not assignment

正式来说,这个初始化使用 S(int) 创建类型为 S 的临时对象,然后使用复制构造函数从临时对象构造对象 s

这与赋值非常不同,赋值处理已经存在的对象:

S s1;
s1 = 3; // assignment

在这里,如果S定义了赋值运算符,那么该赋值将使用赋值运算符。这就是为什么原始代码中的 b = 行起作用的原因。

这个语句有任何依据吗?正式地说,这个初始化使用S(int)来创建一个S类型的临时对象。 - Slava
@Slava - 是的,这是标准要求。自 C++ 98 以前就一直如此。 - Pete Becker
但是效果完全不同,如果我明确创建临时变量,那么它将允许转换,否则是不允许的。请参见我的答案。 - Slava
嗯,有点道理;第二个明确地创建了一个临时变量,并将其复制到s中。但这不是我给出的示例,它隐式地创建了一个临时变量。 - Pete Becker
但是你说过它在正式上确实这样做,因此可以理解为语义是相同的,这是不正确的。 - Slava
显示剩余5条评论

1

首先,尽管编译器没有发出诊断消息,但类定义中的赋值运算符存在问题。

class foo{
private:
    string str;
public:
    foo operator = (string s){
         str = s;
    }
};

该代码无效,因为它的返回类型是foo,但未返回任何内容。

建议改写为:

    foo & operator = ( const std::string &s){
         str = s;
         return *this;
    }

由于您没有声明默认构造函数或复制构造函数,编译器会隐式地代替您声明它们。因此,实际上您的类只有两个构造函数。其中一个是默认构造函数,其声明如下:
foo();

还有以下声明的复制构造函数

for( const foo & );

在这个声明中。
foo a = "this initialization throwing an error";

编译器假定右侧是一个类型为foo的对象,并且您将使用它来创建对象a。也就是说,在此声明中,编译器尝试应用隐式创建的复制构造函数。为此,它需要将字符串字面值转换为foo类型的对象。但是,该类没有转换构造函数,即无法接受字符串字面值作为参数的构造函数。因此,编译器会发出错误:

错误:请求将const char [38]转换为非标量类型'foo'

const char[33]是声明右侧的字符串字面值的类型。

在这个代码片段中:

foo b;
b = "but assignment after declaration is working fine";

首先,使用编译器隐式定义的默认构造函数创建类型为foo的对象b,然后在第二个语句中使用了显式定义在类中的赋值运算符。根据运算符定义,它将数据成员str分配给临时对象,该对象是从使用的字符串字面量构造的std::string类型。这是可能的,因为类std::string具有相应的转换构造函数。


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