Шаблонный конструктор копирования не работает с определенным шаблонным типом

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

Это плохое решение, так как оно приводит к массовому дублированию кода конструктора копирования (буквально неизменное копирование и вставка). Что еще более важно, я не понимаю, почему возникает ошибка double-free 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));
        }
    }
}

person Michael Koval    schedule 08.08.2009    source источник


Ответы (2)


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

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

Кроме того, почему требуется чрезвычайно подробный синтаксис 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)
}
person Johannes Schaub - litb    schedule 08.08.2009
comment
Ваше объяснение ошибки malloc звучит правильно. Есть ли конкретная причина, по которой компиляторы не могут использовать функцию-член шаблона в качестве конструктора копирования? Спасибо за информацию и за совет, чтобы избежать дублирования кода. - person Michael Koval; 09.08.2009
comment
Если у вас есть этот шаблон, это еще не функция. Только использование его с некоторым аргументом шаблона генерирует из него (член-) функцию (называемую специализацией). Это также является причиной того, что шаблоны-члены не могут быть виртуальными: вы не знаете заранее, какие функции будут сгенерированы из него. - person Johannes Schaub - litb; 09.08.2009
comment
Это имеет большой смысл - это не работает по той самой причине, что нужно либо переслать параметры шаблона, либо включить полный исходный код реализации. Спасибо еще раз. - person Michael Koval; 09.08.2009
comment
... шаблон никогда не может служить конструктором копирования. Это немного вводит в заблуждение. Правильное утверждение: конструктор копии шаблона не подавляет конструктор неявной копии, сгенерированный компилятором. Причина, по которой это важно, заключается в том, что если вы предоставляете конструктор копирования не из шаблона, который имеет худшее преобразование, чем конструктор шаблона, то будет выбран конструктор шаблона. Например: Matric(Matric const volarile & m). Обычный случай заключается в том, что неявно сгенерированный ctor побеждает, поскольку он по крайней мере так же хорош, как шаблон, и он не является шаблоном. - person Richard Corden; 10.08.2009
comment
@Richard, хорошее замечание о последовательности преобразования. Я исправлю свой ответ. - person Johannes Schaub - litb; 10.08.2009
comment
@Richard: Верно, но шаблон по-прежнему не является конструктором копирования. - person Ben Voigt; 19.01.2014
comment
@БенВойт верно. Я полагаю, именно поэтому Ричард сказал, что мое утверждение скорее вводит в заблуждение, чем неверно. Это было достаточно вводящее в заблуждение ИМО, поэтому мне пришлось его пересмотреть :) - person Johannes Schaub - litb; 19.01.2014

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

person Community    schedule 08.08.2009