【问题标题】:Templated copy-constructor fails with specific templated type模板复制构造函数因特定模板类型而失败
【发布时间】:2009-08-08 20:29:11
【问题描述】:

由于我的一些代码需要在不同类型的矩阵之间进行隐式转换(例如 Matrix<int>Matrix<double>),我定义了一个模板化的复制构造函数 Matrix<T>::Matrix(Matrix<U> const&) 而不是标准的 Matrix<T>::Matrix(Matrix<T> const&)

template <typename T> class Matrix {
public:
    // ...
    template <typename U> Matrix(Matrix<U> const&);
    // ...
private
    unsigned int m_rows, m_cols;
    T *m_data;
    // ...
};

在复制构造函数中添加适当的类型转换后,此方法可以在不同类型的矩阵之间完美转换。令人惊讶的是,在简单的复制构造函数可以运行的情况下,它会因 malloc 错误而失败:U == T。果然,用默认的Matrix&lt;T&gt;::Matrix(Matrix&lt;T&gt; const&amp;)签名重载copy-constructor解决了这个问题。

这是一个糟糕的解决方案,因为它会导致大量复制构造函数代码(字面意思是未更改的复制和粘贴)。更重要的是,我不明白为什么在没有重复代码的情况下会出现 double-free malloc 错误。此外,为什么这里需要极其冗长的 template &lt;typename T&gt; template &lt;typename U&gt; 语法,而不是标准的,更简洁的 template &lt;typename T, typename U&gt;

模板化方法的完整源代码,在 Mac OS 10.5 上使用 G++ v4.0.1 编译。

template <typename T> template <typename U> Matrix<T>::Matrix(Matrix<U> const& obj) {
    m_rows = obj.GetNumRows();
    m_cols = obj.GetNumCols();
    m_data = new T[m_rows * m_cols];

    for (unsigned int r = 0; r < m_rows; ++r) {
        for (unsigned int c = 0; c < m_cols; ++c) {
            m_data[m_rows * r + c] = static_cast<T>(obj(r, c));
        }
    }
}

【问题讨论】:

    标签: c++ constructor type-conversion copy-constructor


    【解决方案1】:

    它失败是因为模板没有抑制复制构造函数的隐式声明。它将作为一个简单的转换构造函数,可用于在重载决议选择对象时复制对象。

    现在,您可能在某处复制了矩阵,这将使用隐式定义的复制构造函数来执行平面复制。然后,复制的矩阵和副本都将在它们的析构函数中删除相同的指针。

    此外,为什么需要极其冗长的template &lt;typename T&gt; template &lt;typename U&gt; 语法

    因为涉及到两个模板:Matrix(类模板)和转换构造函数模板。每个模板都应该有自己的模板子句和自己的参数。

    顺便说一句,你应该去掉第一行中的&lt;T&gt;。定义模板时不会出现这样的事情。

    这是一个糟糕的解决方案,因为它会导致大量复制构造函数代码

    您可以定义一个成员函数模板来完成工作,并从转换构造函数和复制构造函数进行委托。这样代码就不会重复了。


    Richard 在 cmets 中提出了一个很好的观点,这让我修改了我的答案。如果从模板生成的候选函数比隐式声明的复制构造函数更匹配,那么模板“获胜”,它将被调用。下面是两个常见的例子:

    struct A {
      template<typename T>
      A(T&) { std::cout << "A(T&)"; }
      A() { }
    };
    
    int main() {
      A a;
      A b(a); // template wins:
              //   A<A>(A&)  -- specialization
              //   A(A const&); -- implicit copy constructor
              // (prefer less qualification)
    
      A const a1;
      A b1(a1); // implicit copy constructor wins: 
                //   A(A const&) -- specialization
                //   A(A const&) -- implicit copy constructor
                // (prefer non-template)
    }
    

    复制构造函数也可以有一个非常量引用参数,如果它的任何成员有

    struct B { B(B&) { } B() { } };
    struct A {
      template<typename T>
      A(T&) { std::cout << "A(T&)"; }
      A() { }
      B b;
    };
    
    int main() {
      A a;
      A b(a); // implicit copy constructor wins:
              //   A<A>(A&)  -- specialization
              //   A(A&); -- implicit copy constructor
              // (prefer non-template)
    
      A const a1;
      A b1(a1); // template wins: 
                //   A(A const&) -- specialization
                // (implicit copy constructor not viable)
    }
    

    【讨论】:

    • 您对 malloc 错误的解释听起来很准确。为什么编译器不能将模板成员函数用作复制构造函数,是否有特定原因?感谢您提供信息和避免代码重复的建议。
    • 如果你有那个模板,它还不是一个函数。仅将它与某些模板参数一起使用会从中生成一个(成员)函数(称为特化)。这也是成员模板不能虚拟的原因:你事先并不知道从中生成了哪些函数。
    • 这很有意义 - 这不起作用,因为需要转发声明模板参数或包含实现的完整源代码。再次感谢。
    • "..模板永远不能用作复制构造函数。":这有点误导。正确的说法是:“模板复制构造函数不会抑制编译器生成的隐式复制构造函数”。这很重要的原因是,如果您提供的非模板复制构造函数的转换比模板构造函数差,那么将选择模板构造函数。例如:“Matric(Matric const volarile & m)”。通常的情况是隐式生成的 ctor 获胜,因为它至少与模板一样好,而且它不是模板。
    • @Richard,关于转换顺序的要点。我会修改我的答案。
    【解决方案2】:

    您的问题我并不完全清楚,但我怀疑正在发生的事情是默认复制构造函数(仅执行成员复制)正在您的代码中的某些地方使用。请记住,不仅您实际编写的代码使用复制构造函数——编译器也使用它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-22
      • 2011-05-08
      • 2015-12-08
      • 1970-01-01
      • 2011-05-24
      • 2011-09-15
      • 1970-01-01
      相关资源
      最近更新 更多