Перегрузка глобального свопа для пользовательского типа - PullRequest
12 голосов
/ 08 февраля 2010

Стандарт C ++ запрещает объявлять типы или определять что-либо в пространстве имен std, но он позволяет специализировать стандартные шаблоны STL для пользовательских типов.

Обычно, когда я хочу специализировать std::swap для моего собственного настраиваемого типа, я просто делаю:

namespace std
{
  template <class T>
  void swap(MyType<T>& t1, MyType<T>& t2)
  {
     t1.swap(t2);
  }
}

... и это прекрасно работает. Но я не совсем уверен, что моя обычная практика соответствует стандарту. Я делаю это правильно?

Ответы [ 7 ]

15 голосов
/ 08 февраля 2010

То, что у вас есть, это не специализация, это перегрузка и именно то, что стандарт запрещает. (Однако в настоящее время это почти всегда работает на практике и может быть приемлемо для вас.)

Вот как вы предоставляете собственный своп для шаблона вашего класса:

template<class T>
struct Ex {
  friend void swap(Ex& a, Ex& b) {
    using std::swap;
    swap(a.n, b.n);
  }
  T n;
}

А вот как вы называете своп, который, как вы заметили, используется и в свопе Ex:

void f() {
  using std::swap; // std::swap is the default or fallback
  Ex<int> a, b;
  swap(a, b); // invokes ADL
}

Похожие: Важность и необходимость специализации шаблона функции

3 голосов
/ 08 февраля 2010

Из-за поиска, зависящего от аргумента (он же Кениг), я полагаю, что вы можете указать свой собственный своп в пространстве имен того типа, для которого вы его хотите, и он будет найден в предпочтении ::std::swap. Кроме того, я считаю, что шаблон для ::std::swap будет расширяться по-разному для классов, которые имеют свою собственную функцию-член swap, и поэтому вы можете добавить эту функцию-член в класс, и она будет использоваться для вашего типа.

3 голосов
/ 08 февраля 2010

Почему бы вам просто не определить swap в пространстве имен MyType и использовать возможности поиска, зависящие от аргументов?

1 голос
/ 08 февраля 2010

Редактировать

См. Статью Скотта Мейера: См. Эффективное C ++, 3-е издание , пункт 25. Рассмотрите возможность поддержки безброскового свопа (p106-p112) для подтверждения моего ответа.

Оригинальный ответ

Скотт Мейерс писал об этом, поэтому мой ответ приходит из памяти.

Сначала определите функцию подкачки в пространстве имен вашего класса. Например:

namespace MyNamespace
{
   class MyClass { /* etc. */ } ;

   template<typename T>
   class MyTemplate { /* etc. */ } ;

   void swap(MyClass & lhs, MyClass & rhs)
   {
      // the swapping code (**)
   }

   template<typename T>
   void swap(MyTemplate<T> & lhs, MyTemplate<T> & rhs)
   {
      // the swapping code (**)
   }
}

Затем, , если возможно (это не всегда возможно для шаблонных классов (*)), специализировать функцию подкачки в пространстве имен std. Например:

namespace std
{
   template<>
   void swap<MyNamespace::MyClass>(MyNamespace::MyClass & lhs, MyNamespace::MyClass & rhs)
   {
      // the swapping code (**)
   }

   // The similar code for MyTemplate is forbidden, so don't try
   // to uncomment it
   //
   // template<typename T>
   // void swap<MyNamespace::MyTemplate<T> >(MyNamespace::MyTemplate<T> & lhs, MyNamespace::MyTemplate<T> & rhs)
   // {
   //   // the swapping code (**)
   // }
}

При использовании функции подкачки сделайте это косвенно, импортировав функцию подкачки std в вашу область видимости. Например:

void doSomething(MyClass & lhs, MyClass & rhs)
{
   // etc.

   // I swap the two objects below:
   {
      using std::swap ;
      swap(lhs, rhs) ;
   }

   // etc.
}

void doSomethingElse(MyTemplate<int> & lhs, MyTemplate<int> & rhs)
{
   // etc.

   // I swap the two objects below:
   {
      using std::swap ;
      swap(lhs, rhs) ;
   }

   // etc.
}

Как только у меня будет доступ к моим книгам, я опубликую здесь точную ссылку.

  • (*) шаблон частичной специализации функции запрещен
  • (**), конечно, хорошим примером является метод «swap», объявленный в классе, функции swap, вызывающие метод swap, и пользователь, вызывающий функцию swap.
0 голосов
/ 08 февраля 2013

То, что вы делаете, - это перегрузка, а не специализация шаблона. Стандарт не допускает перегрузки внутри namespace std (17.6.4.2.1 §1)

Поведение программы на C ++ не определено, если она добавляет объявления или определения к namespace std или к пространству имен в namespace std, если не указано иное. Программа может добавить специализацию шаблона для любого шаблона стандартной библиотеки в namespace std, только если объявление зависит от типа, определенного пользователем, и специализация соответствует требованиям стандартной библиотеки для исходного шаблона и явно не запрещена.

Поэтому предпочитайте помещать ваш тип шаблона в ваше собственное пространство имен и определять не член swap() в этом пространстве имен (это не является строго необходимым, но хорошая практика). Таким образом, swap(x,y) будет работать из любого места через зависимый от аргумента поиск (ADL, также известный как поиск Кенига), если x или y находятся в вашем пространстве имен.

namespace my_ns {

template <typename T> class MyType
{
public:
    void swap( MyType & other ) noexcept;
};

template <typename T>
void swap( MyType<T> & lhs, MyType<T> & rhs ) noexcept
{
    lhs.swap(rhs);
}

} // namespace my_ns

Код, использующий swap(), должен обычно использовать технику using namespace std. Таким образом, ваша версия свопа будет найдена ADL, и она будет предпочтительнее функции std::swap(), так как она более специализированная.

// client code
MyType<Bla> x, y;
/* ... some code ... */
using namespace std;
swap( x, y ); // will call your swap version
0 голосов
/ 08 февраля 2010

Определить свой swap. Эта функция должна вызывать std :: swap для любого типа T, кроме ваших типов.

namespace help // my namespace
{ 

  template <class T> 
  void swap(T& t1, T& t2) 
  { 
     ::std::swap(t1, t2);  // Redirect to std for almost all cases
  } 

  // My special case: overloading
  template <class T> 
  void swap(MyType<T>& t1, MyType<T>& t2) 
  { 
     t1.swap(t2); 
  } 

}  //  namespace help 

// Sample
int main() 
{

   MyType<int> t1, t2; // may be add initialization
   int i1=5, i2=7;

   help::swap(t1, t2); //  Your swap
   help::swap(i1, i2); //  Redirect to std::swap
}
0 голосов
/ 08 февраля 2010

Определите ваш тип и функцию подкачки в одном и том же пространстве имен:

namespace foo
{
   struct Bar
   {
   };

   void swap(Bar & t1, Bar& t2)
   {
     // whatever
   }
}

int main()
{
    using std::swap;
    foo::Bar a, b;
    swap(a, b); // Argument-dependent lookup chooses foo::swap
                // if it exists, or else reverts to std::swap
}
...