2010-05-09 2 views
13

Мой коллега рассказал мне о небольшом дизайне, который он использовал со своей командой, которая послала мой ум кипения. Это своего рода черт класс, который они могут специализировать в чрезвычайно разобранном виде.Неопределенные методы трюков шаблонов?

Мне было трудно понять, как это может работать, и я все еще не уверен в своей идее, поэтому я подумал, что попрошу о помощи здесь.

Мы говорим о g ++ здесь, в частности версии 3.4.2 и 4.3.2 (похоже, они работают с обоими).

Идея довольно проста:

1- Определить интерфейс

// interface.h 
template <class T> 
struct Interface 
{ 
    void foo(); // the method is not implemented, it could not work if it was 
}; 

// 
// I do not think it is necessary 
// but they prefer free-standing methods with templates 
// because of the automatic argument deduction 
// 
template <class T> 
void foo(Interface<T>& interface) { interface.foo(); } 

2- Определение класса, и в исходном файле специализировать интерфейс для этого класса (определение его методы)

// special.h 

class Special {}; 


// special.cpp 

#include "interface.h" 
#include "special.h" 

// 
// Note that this specialization is not visible outside of this translation unit 
// 
template <> 
struct Interface<Special> 
{ 
    void foo() { std::cout << "Special" << std::endl; } 
}; 

3- Для использования, это просто слишком:

// main.cpp 

#include "interface.h" 

class Special; // yes, it only costs a forward declaration 
       // which helps much in term of dependencies 

int main(int argc, char* argv[]) 
{ 
    Interface<Special> special; 
    foo(special); 
    return 0; 
}; 

Это неопределенный символ, если единица перевода не определила специализацию Interface для Special.

Теперь я бы подумал, что для этого потребуется ключевое слово export, которое, насколько мне известно, никогда не было реализовано в g ++ (и реализовано только один раз в компиляторе на C++, а его авторы советуют кому-то не делать этого, учитывая время и усилия он их взял).

Я подозреваю, что-то делать с линкера разрешающего методы шаблонов получил ...

  • ли вы когда-либо встречал ничего подобного раньше?
  • Соответствует ли это стандарту или, по вашему мнению, это удачное совпадение, это работает?

Я должен признать, что я совершенно озадачен конструкт ...

ответ

10

Как и подозреваемый @Steward, это недействительно.Формально это эффективно, вызывая неопределенное поведение, потому что Стандарт устанавливает, что для нарушения не требуется никакой диагностики, а это означает, что реализация может молча делать все, что захочет. В 14.7.3/6

Если шаблон, шаблон члена или член шаблона класса явно специализирован то, что специализация должна быть объявлена ​​перед первым использованием этой специализации, что приведет к неявной конкретизации иметь место, в каждом единица перевода, в которой такое использование происходит; диагностика не требуется.

На практике, по крайней мере на GCC, это неявно инстанцировании основной шаблон Interface<T>, поскольку специализация не была объявлена ​​и не видно в main, а затем вызвать Interface<T>::foo. Если его определение видимо, оно инициализирует основное определение функции-члена (поэтому, когда оно определено, оно не будет работать).

Символы с именами исполняемых функций имеют слабую связь, поскольку они могут присутствовать несколько раз в разных объектных файлах и должны быть объединены в один символ в финальной программе. Напротив, члены явных специализаций, которые не являются шаблонами, больше имеют прочную связь, поэтому они будут доминировать над слабыми символами привязки и превращать вызов в специализацию. Все это детализация реализации, и в Стандарте нет такого понятия слабой/сильной связи. Вы должны объявить специализацию до создания special объекта:

template <> 
struct Interface<Special>; 

Стандарт закладывает это голое (подчеркнуть мной)

размещения явных деклараций специализации для шаблонов функций, шаблонов классов, член функции шаблонов классов, статические элементы данных шаблонов классов, классы-члены шаблонов классов, шаблоны классов членов шаблонов классов, шаблоны-члены шаблонов классов, функции-члены шаблонов-членов шаблонов классов, функции-члены шаблонов-членов не-шаблона классы, шаблоны функций-членов классов-членов c шаблоны ласс и т. д., а также размещение деклараций частичной специализации шаблонов классов, шаблонов классов членов классов, отличных от шаблонов, шаблонов классов членов шаблонов классов и т. д., может повлиять на правильность формирования программы в соответствии с относительным позиционированием явных деклараций специализации и их точек инстанцирования в блоке перевода, как указано выше и ниже. При написании специализации будьте осторожны с ее местоположением; или сделать его компиляцией, будет таким испытанием, чтобы разжечь его самосожжение.

+0

Таким образом, если я правильно понимаю, в «special.h» было бы достаточно включить «interface.h» и переслать объявление специализации шаблона «template <> struct Interface ;», а затем в «main.cpp» включить «special.h», чтобы сделать программу корректной, даже если определение метода «Интерфейс :: foo' никогда не появится? –

+0

@Matthieu, точно!Вот как вы можете решить страдания –

+0

Если я прочитал это правильно, это означает, что любая программа (несколько единиц перевода), где существует явная специализация шаблона для заданного набора параметров, но в какой-либо другой единицы перевода неявная специализация используется для один и тот же набор параметров - это нарушение ODR, которое компилятор не требует - и, вероятно, не может - выдать диагностику? Это довольно страшно и еще одна веская причина всегда ставить частичные специализации прямо с основным шаблоном, где это возможно. – Stewart

5

Thats очень аккуратно. Я не уверен, что гарантировано работать везде. Похоже, что они делают, имея намеренно неопределенный шаблонный метод, а затем определяя специализацию, спрятанную в своей собственной единицы перевода. Они зависят от компилятора, использующего одно и то же имя, как для исходного метода шаблона класса, так и для специализации, который, по-моему, является нестандартным. Затем компоновщик будет искать метод шаблона класса, но вместо этого найдет специализацию.

Есть несколько рисков с этим. Никто, даже не компоновщик, не возьмет несколько реализаций метода, например. Методы шаблона будут отмечены selectany, потому что шаблон подразумевает inline, поэтому, если компоновщик видит несколько экземпляров, вместо того, чтобы выдавать ошибку, он выбирает тот, который наиболее удобен.

Тем не менее, хороший трюк, хотя, к сожалению, похоже, что это удачное совпадение, что оно работает.

+0

Ну, проблема связи возникает для любого определения метода шаблона, хотя риск выше по специализации, которую я допускаю. Здесь они полагаются на соглашение (специализация находится в соответствующем исходном файле), чтобы предотвратить нарушение правила One Definition. Можно отметить, что если шаблонный метод был определен для общего случая, то ODR будет нарушен, потому что общий шаблон будет инстанцирован. Хороший вопрос по проблеме смены названия, мне и не приходило в голову, что там может быть проблема. –