Шаблонный конструктор копирования терпит неудачу с определенным шаблонным типом - PullRequest
8 голосов
/ 09 августа 2009

Поскольку в моем коде требовалось неявное преобразование между матрицами разных типов (например, 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<T>::Matrix(Matrix<T> const&) по умолчанию решает проблему.

Это плохое решение, поскольку оно приводит к массовому дублированию кода конструктора копирования (буквально неизменного копирования и вставки). Что еще более важно, я не понимаю, почему возникает ошибка двойного освобождения malloc без дублирующего кода. Кроме того, почему здесь требуется чрезвычайно многословный синтаксис template <typename T> template <typename U>, в отличие от стандарта, и гораздо более краткий template <typename T, typename U>?

Полный источник шаблонного метода, скомпилированного с использованием G ++ v4.0.1 в Mac OS 10.5.

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));
        }
    }
}

Ответы [ 2 ]

13 голосов
/ 09 августа 2009

Сбой, потому что шаблон не подавляет неявное объявление конструктора копирования. Он будет служить простым конструктором преобразования, который можно использовать для копирования объекта, когда его выбирает разрешение перегрузки.

Теперь вы, вероятно, скопировали свою матрицу куда-нибудь, в которой использовался бы неявно определенный конструктор копирования, который делает плоскую копию. Затем скопированная матрица и копия в своем деструкторе удалят один и тот же указатель.

Кроме того, почему требуется чрезвычайно подробный template <typename T> template <typename U> синтаксис

Потому что задействованы два шаблона: матрица, которая является шаблоном класса, и шаблон конструктора преобразования. Каждый шаблон заслуживает отдельного предложения шаблона со своими параметрами.

Кстати, вы должны избавиться от <T> в первой строке. Такая вещь не появляется при определении шаблона.

Это плохое решение, так как оно приводит к массовому дублированию кода конструктора копирования

Вы можете определить шаблон функции-члена, который будет выполнять работу, и делегировать как конструктор преобразования, так и конструктор копирования. Таким образом, код не дублируется.


Ричард высказал хорошее замечание в комментариях, которые заставили меня изменить свой ответ. Если функция-кандидат, сгенерированная из шаблона, лучше соответствует неявно объявленному конструктору копирования, тогда шаблон «выигрывает», и он будет вызван. Вот два распространенных примера:

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)
}
1 голос
/ 09 августа 2009

Мне не совсем понятно из вашего вопроса, но я подозреваю, что происходит то, что конструктор копирования по умолчанию (который выполняет только членское копирование) используется в некоторых местах в вашем коде. Помните, что не только код, который вы фактически пишете, использует конструктор копирования - компилятор также использует его.

...