在模板成员转换运算符中编译错误

时间:2012-04-21 02:53:13

标签: c++ compiler-errors auto-ptr function-templates

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

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 ++,结果相似。有人可以解释一下这里发生了什么吗?

2 个答案:

答案 0 :(得分:1)

问题在于您的复制构造函数。它的参数是TestPtr&(非const引用):

TestPtr(TestPtr& rhs)

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

通常解决方案是让你的拷贝构造函数采用const引用。但是,在这种情况下,解决方案有点棘手;你会想做一些与std::auto_ptr类似的事情。我在回答"How could one implement std::auto_ptr's copy constructor?"

时描述了它的肮脏技巧

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


另请注意,您必须提供用户声明的复制赋值运算符。 (或者,如果您决定将类型设置为仅移动,则需要提供用户声明的移动赋值运算符并禁止隐式复制赋值运算符,再次使用= delete或通过声明而不是定义它。 )

答案 1 :(得分:0)

在你的拷贝构造函数中,你出于某种原因坚持将源指针归零

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

你为什么这样做?

由于你坚持将rhs归零,你不能将参数声明为const TestPtr& rhs,这反过来会破坏你的代码,正如詹姆斯解释的那样。

我认为没有理由将源指针置零。只是做

TestPtr(const TestPtr& rhs) : _pointee(rhs._pointee)
  {}

它应该有用。

我怀疑你使用std::auto_ptr获取灵感,并在复制程序中看到类似的东西。但是,在std::auto_ptr中,源指针被清零,因为std::auto_ptr获取它指向的对象的所有权,并在复制时转移该所有权。所有权管理的需要取决于std::auto_ptr不仅指向对象,还试图自动销毁它们。

你的指针不会试图破坏任何东西。它不需要获得指向对象的所有权。因此,在复制指针时不需要将源清零。