我试图在一个类中编写一个转换运算符函数模板,并遇到一些我不完全理解的编译错误。

class ABC { };

class BBC:public ABC { };

template <class T>
class TestPtr
{
    public:
        TestPtr(T* ptr=0)
            : _pointee(ptr)
        {   }

        TestPtr(TestPtr& rhs)
        {
            this->_pointee = rhs._pointee;
            rhs._pointee= 0;
        }

        template <class U> operator TestPtr<U>();

    private:
        T* _pointee;
};

template <class T> template <class U>
TestPtr<T>::operator TestPtr<U>()
{
    return TestPtr<U>(this->_pointee);   // if this line is changed to
    //TestPtr<U> x(this->_pointee);      // these commented lines, the
    //return x;                          // compiler is happy
}

void foo (const TestPtr<ABC>& pmp)
{  }

int main() {
    TestPtr<BBC> tbc(new BBC());
    foo(tbc);
}

上面的代码导致以下错误
TestPtr.cpp: In member function ‘TestPtr<T>::operator TestPtr<U>() [with U = ABC, T = BBC]’:
TestPtr.cpp:38:9:   instantiated from here
TestPtr.cpp:28:34: error: no matching function for call to ‘TestPtr<ABC>::TestPtr(TestPtr<ABC>)’
TestPtr.cpp:28:34: note: candidates are:
TestPtr.cpp:13:3: note: TestPtr<T>::TestPtr(TestPtr<T>&) [with T = ABC, TestPtr<T> = TestPtr<ABC>]
TestPtr.cpp:13:3: note:   no known conversion for argument 1 from ‘TestPtr<ABC>’ to ‘TestPtr<ABC>&’
TestPtr.cpp:9:3: note: TestPtr<T>::TestPtr(T*) [with T = ABC]
TestPtr.cpp:9:3: note:   no known conversion for argument 1 from ‘TestPtr<ABC>’ to ‘ABC*’

现在让我感到困惑的是,编译器正在尝试在return语句中选择TestPtr<ABC>::TestPtr(TestPtr<ABC>)而不是TestPtr<ABC>::TestPtr(ABC *)。但是,如果我先使用预期的构造函数创建一个变量,然后返回该值,则它可以正常工作。我也使T *构造函数明确无济于事。

我已经尝试过用g++和clang++取得相似的结果。有人可以解释一下这是怎么回事吗?

最佳答案

问题出在您的复制构造函数上。它的参数是TestPtr&(非常量引用):

TestPtr(TestPtr& rhs)

非常量引用不能绑定(bind)到右值表达式。 TestPtr<U>(this->_pointee)是一个创建临时对象的右值表达式。当您尝试直接返回该对象时,必须进行复制。因此,当编译器无法调用复制构造函数时,会出现此错误。

通常的解决方案是让您的副本构造函数使用const引用。但是,在这种情况下,解决方案比较棘手。您将想要做类似于std::auto_ptr的操作。我在对"How could one implement std::auto_ptr's copy constructor?"的回答中描述了它的肮脏技巧

另外,如果您使用的是最新的C++编译器,并且只需要支持支持右值引用的编译器,则可以通过提供用户声明的move构造函数(TestPtr(TestPtr&&))并抑制复制构造函数(将其声明为=delete)来使您的类可移动但不可复制(如果您的编译器支持已删除的成员函数,或者将其声明为私有(private)且未定义)。

还要注意,您必须提供用户声明的副本分配运算符。 (或者,如果您决定使类型为仅移动类型,则需要再次使用= delete或通过声明而不定义它,来提供用户声明的移动分配运算符并取消隐式副本分配运算符。)

09-27 19:42