3

为什么以下两个用例的行为不同?

内置类型的示例:

class Test
{
    operator const char*() {
        return "operator const char*() called";
    }

    operator char*() {
        return const_cast<char*> ("operator char*() called");
        // a really ugly hack for demonstration purposes
    }
};

Test test;
const char *c_ch = test;  // -> operator const char*() called
char *ch = test;          // -> operator char*() called

好的,一切正常。不,让我们用用户定义的类型来试试:

class ColorWrapper
{
    operator const Color() {
        cout << "operator const Color() called" << endl;
        return Color(10, 20, 30);
    }

    operator Color() {
        cout << "operator Color() called" << endl;
        return Color(11, 22, 33);
    }
};

ColorWrapper cw;
const Color c_col = cw;
Color col = cw;

情况看起来相同;但现在,GCC 开始抱怨:

error: conversion from 'ColorWrapper' to 'const Color' is ambiguous
error: conversion from 'ColorWrapper' to 'Color' is ambiguous

我错过了什么吗?我在这里想要实现的是防止用户直接更改颜色。如果他们想改变它,他们必须通过包装类来完成。

4

4 回答 4

3

问题是,你的两个例子是不等价的。第一个返回编译器可以轻松选择的两种不同类型,一个指向可变数据的指针和一个指向常量数据的指针。会有所不同,它是指针还是常量指针(这意味着您无法更改返回的指针指向的位置,而不是更改指向的数据):

class Test
{
    operator char * const();
    operator char *();
};

这将等同于您的第二个示例并引入相同的歧义,因为编译器无法决定选择哪一个,因为两者都返回相同的类型,一个是 const 限定的,另一个不是。

这可以通过基于对象的常量重载来解决:

class ColorWrapper
{
    operator const Color() const;
    operator Color();
};

但这在这里不会给您带来任何好处,因为无论如何您都按值返回一个新对象,因此您始终可以返回 const 版本,并且还将根据源对象的 constness 而不是目标对象的 constness 选择适当的版本(因此在这两种情况下都调用非常量版本),这似乎不是你想要的。

因此,您可以根据源对象的常量重载。但是你不能像现在想要的那样,仅仅基于目标对象的常量来重载。

这一切都归结为考虑对象和对象可能不正确地引用的数据之间的差异,这实际上表现为常量指针和指向常量数据的指针之间的差异。

我在这里想要实现的是防止用户直接更改颜色

那么绝对不需要任何重载。只需返回 const 版本。用户无法直接更改返回的颜色,只能复制并更改副本。

cw.set(red);    //nope, return color is constant
Color col = cw; //copy returned color into col
col.set(green); //fine, since we're not working on the returned color, but on col

const Color c_col = cw; //use this if you don't want c_col to be modified, 
                        //doesn't have anything to do with returned color, though
于 2012-10-16T13:24:14.463 回答
2

那是因为在您的第二个示例中,您将 const 添加到 object ,而不是指针(在您的第一种情况下)。并且 const 与 object 在重载解析阶段使编译混淆,根据 c++ 标准,这两者彼此一样好(object vs const object)。编译器不会在这里尝试智能并选择另一个。

但是在指针与常量指针(参考也有效)中,编译可以相应地从这两个重载候选中进行选择,这是您的第一种情况。

于 2012-10-16T12:55:25.570 回答
0

如果您将一个运算符修改为const,它将起作用:

operator const Color() const {
    cout << "operator const Color() const called" << endl;
    return Color(10, 20, 30);
}

或者,您可以删除两个运算符之一。由于您返回Color按值,因此两个运算符之间没有真正的区别。

于 2012-10-16T12:48:33.433 回答
0

你观察到这个问题是因为你在这里基本上有两种不同的情况。我认为展示这里发生的事情的最简单方法是修改你的第一个例子:

class Test
{
    operator char* const() {
        return "operator char* () called";
    }

    operator char*() {
        return const_cast<char*> ("operator char*() called");
        // a really ugly hack for demonstration purposes
    }
};

现在您的情况与第二个示例完全相同。您基本上返回一个(指针的)副本和一个 const 副本,这就是它们在重载决议中搞砸的原因。

您最初的第一个示例工作正常,因为这些转换运算符转换为 2 种不同类型(const charchar)的指针。我希望这有助于理解这里的区别。

顺便说一句,我个人认为从方法返回 const 副本没有多大意义。它不会改变用户的任何东西,只会混淆类接口。

于 2012-10-16T13:44:55.360 回答