2009-08-08 16 views
8

Como algunos de mi código requerido conversión implícita entre matrices de diferentes tipos (por ejemplo Matrix<int> a Matrix<double>), he definido un constructor de copia de plantilla Matrix<T>::Matrix(Matrix<U> const&) en lugar de la estándar Matrix<T>::Matrix(Matrix<T> const&):con plantilla constructor de copia falla con el tipo de plantilla específica

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

Con un tipo de conversión apropiado agregado al copiador-constructor, este método se convierte sin problemas entre matrices de diferentes tipos. Sorprendentemente, falla con un error malloc en la misma situación donde funcionaría un simple constructor de copia: donde U == T. Efectivamente, la sobrecarga del copy-constructor con la firma predeterminada Matrix<T>::Matrix(Matrix<T> const&) resuelve el problema.

Esta es una solución deficiente, ya que resulta en la duplicación al por mayor del código de copia y constructor (Literalmente una copia y pegado sin cambios). Más importante aún, no entiendo por qué hay un error doble malloc sin el código duplicado. Además, ¿por qué es necesaria la sintaxis extremadamente detallada de template <typename T> template <typename U> aquí en lugar de la estándar, y mucho más sucinta, template <typename T, typename U>?

Fuente completa del método con plantilla, compilado con G ++ v4.0.1 en 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)); 
     } 
    } 
} 

Respuesta

13

No funciona porque una plantilla no suprime la declaración implícita de un constructor de copia. Servirá como un simple constructor de conversión, que se puede usar para copiar un objeto cuando la resolución de sobrecarga lo selecciona.

Ahora, probablemente haya copiado su matriz en alguna parte, que usaría el constructor de copia definido implícitamente que hace una copia plana. Entonces, la matriz copiada y la copia eliminarían el mismo puntero en su destructor.

Por otra parte, ¿por qué es la sintaxis extremadamente verboso template <typename T> template <typename U> requiere

Debido a que hay dos plantillas implicadas: The Matrix, que es una plantilla de clase, y la plantilla de constructor de conversión. Cada plantilla merece su propia cláusula de plantilla con sus propios parámetros.

Debería deshacerse del <T> en su primera línea, por cierto. Tal cosa no aparece cuando se define una plantilla.

Ésta es una mala solución, ya que da lugar a la duplicación por mayor del código constructor copia

Se puede definir una plantilla de función miembro, que va a hacer el trabajo, y delegado tanto de la convirtiendo el constructor y el constructor de copia. De esa forma, el código no está duplicado.


Richard hizo un buen punto en los comentarios que me hicieron enmendar mi respuesta. Si la función candidata generada a partir de la plantilla es una mejor coincidencia que el constructor de copia declarado implícitamente, la plantilla "gana" y se llamará.Éstos son dos ejemplos comunes:

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

Un constructor de copia puede tener un parámetro de referencia no constante también, si cualquiera de sus miembros tiene

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) 
} 
+0

Tu explicación del error de malloc suena perfectamente. ¿Hay alguna razón específica por la cual los compiladores no pueden usar una función de miembro de plantilla como copia-constructor? Gracias por la información y los consejos para evitar la duplicación de código. –

+0

Si tiene esa plantilla, todavía no es una función. Solo el uso de este con un argumento de plantilla genera una función (miembro) a partir de él (llamada especialización). Esta es también la razón por la cual las plantillas de miembros no pueden ser virtuales: usted no sabe de antemano qué funciones se generan a partir de ella. –

+0

Eso tiene mucho sentido; esto no funciona por la sencilla razón de que uno necesita reenviar los parámetros de la plantilla o incluir el origen completo de la implementación. Gracias de nuevo. –

1

No estoy del todo claro por su pregunta, pero Sospecho que lo que está sucediendo es que el constructor de copia predeterminado (que solo hace una copia para miembros) está siendo utilizado en algunos lugares en su código. Recuerde, no solo el código que realmente escribe usa el constructor de copia, el compilador también lo usa.

Cuestiones relacionadas