czwartek, 15 lipca 2010

ADL w... C++

ADL tym razem oznacza jednak Argument Dependent (Name) Lookup. Jest to ciekawy i kontrowersyjny ficzer języka C++ ;)

ADL dotyczy sytuacji w tym języku, kiedy nazwa funkcji może być podana bez kwalifikatora przestrzeni nazw - odpowiednia przestrzeń nazw jest ustalana na podstawie przestrzeni nazw argumentów funkcji. Dokładniej rzecz biorąc - przy poszukiwaniu danej funkcji (czyli ustalaniu które przeciążenie funkcji mamy na myśli) przeszukiwane są także przestrzenie nazw, do których należą typy argumentów tej funkcji. Warunkiem stosowania ADL jest oczywiście wywołanie danej funkcji bez kwalifikatora przestrzeni nazw.

Właściwość ta wydaje się zaawansowana, ale... można ją pokazać na prostym przykładzie:

#include <iostream>

int main()
{
  std::cout << "Hello World!" << std::endl;
  return 0;
}


Sorry, prostszego nie ma ;) To gdzie tu jest ADL? (nie mylić z adlem ;) )

Zobaczmy, że operator << w:

std::cout << "Hello World!" << std::endl;

to tak naprawdę wywołanie odpowiedniego przeciążenia funkcji operator<< - dokładniej to

std::ostream& std::operator<<(std::ostream&, const char*)

OK... ale widzimy, że wywoływany jest operator<< z przestrzeni nazw std, a przecież nie kwalifikowaliśmy w naszym kodzie tego operatora tą przestrzenią nazw - magia ADL :)

Dobra, brzmi rozsądnie, to gdzie te kontrowersje?

Typy i funkcje powinny znajdować się w osobnych przestrzeniach nazw, chyba że są stworzone aby ze sobą ściśle współpracować. ADL łamie tą zasadę i może "odnaleźć" funkcje, których nie mieliśmy na myśli, ponieważ w poszukiwaniu danej funkcji przegląda całą przestrzeń nazw - nie tylko te funkcje, które potencjalnie mogą nas interesować, bo np. mają w swojej sygnaturze typ, który zainicjował poszukiwania w danej przestrzeni nazw.

Rozważmy taki kod:

#include <memory>
#include <algorithm>

namespace lib {
  class A;
}

namespace my {
  typedef std::auto_ptr<lib::A> handle;

  // niefortunnie nazwana funkcja
  void swap(handle const& a, handle& b);

  void f(handle a, handle b) {
    // ...
    swap(a, b); // std::swap !!!
    // ...
  }
}


W naszej przestrzeni nazw używamy automatycznego wskaźnika do jakiejś klasy z innej przestrzeni nazw i napisaliśmy funkcję, którą (natchnieni) nazwaliśmy swap. Już pewnie wiecie czemu linijka:

swap(a, b);

się nie kompiluje. No właśnie, ADL. Argument a jest typu std::auto_ptr<lib::A>, czyli zgodnie z tą zasadą przeszukujemy całą przestrzeń nazw std. I znajdujemy swap, ale to nie to o które nam chodziło...

Rozwiązaniem jest (obok zmiany nazwy funkcji heh) jest wywoływanie jej wraz z kwalifikatorem przestrzeni nazw:

#include <memory>
#include <algorithm>

namespace lib {
  class A;
}

namespace my {
  typedef std::auto_ptr<lib::A> handle;

  // niefortunnie nazwana funkcja
  void swap(handle const& a, handle& b);

  void f(handle a, handle b) {
    // ...
    my::swap(a, b); // OK
    // ...
  }
}


Ale rozwiązanie to wygląda jak sami przyznacie co najmniej dziwnie.. Cóż, taki jest już ten C++ ;)

Więcej:
http://en.wikipedia.org/wiki/Argument_dependent_name_lookup
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2103.pdf

Brak komentarzy:

Prześlij komentarz