Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 12 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
12
Dung lượng
48,42 KB
Nội dung
addressof Header: "boost/utility.hpp" When taking the address of a variable, we typically depend on the returned value to be, well, the address of the variable. However, it's technically possible to overload operator&, which means that evildoers may be on a mission to wreak havoc on your address-dependent code. boost::addressof is provided to get the address anyway, regardless of potential uses and misuses of operator overloading. By using some clever internal machinery, the template function addressof ensures that it gets to the actual object and its address. Usage To always be sure to get the real address of an object, use boost::addressof. It is defined in "boost/utility.hpp". It is used where operator& would otherwise be used, and it accepts an argument that is a reference to the type whose address should be taken. #include "boost/utility.hpp" class some_class {}; int main() { some_class s; some_class* p=boost::addressof(s); } Before seeing more details on how to use addressof, it is helpful to understand why and how operator& may not actually return the address of an object. Quick Lesson for Evildoers If you really, really, really need to overload operator&, or just want to experiment with the potential uses of operator overloading, it's actually quite easy. When overloading operator&, the semantics are always different from what most users (and functions!) expect, so don't do it just to be cute; do it for a very good reason or not at all. That said, here's a code-breaker for you: class codebreaker { public: int operator&() const { return 13; } }; With this class, anyone who tries to take the address of an instance of codebreaker is handed the magical number 13. template <typename T> void print_address(const T& t) { std::cout << "Address: " << (&t) << '\n'; } int main() { codebreaker c; print_address(c); } It's not hard to do this, but are there good arguments for ever doing it in real code? Probably not, because it cannot be made safe except when using local classes. The reason for this is that while it is legal to take the address of an incomplete type, it is undefined behavior to do so on an incomplete class with a user-defined operator& . Because we cannot guarantee that this won't happen, we're better off not overloading operator&. Quick Remedy for Others Even when operator& is supplied by the class, it is possible to get to the real address of instances of the class. addressof performs some clever work [6] behind the scenes to get to the bottom of the address issue, regardless of any operator& chicanery. If you adjust the function (print_address) to make use of addressof, you'll get what we came here for: [6] Also known as an ingenious hack. template <typename T> void print_address(const T& t) { std::cout << "&t: " << (&t) << '\n'; std::cout << "addressof(t): " << boost::addressof(t) << '\n'; } When invoked, the function gives this output (or similar, because the exact address differs depending upon your system). &t: 13 addressof(t): 0012FECB13 That's more like it! If there are scenarios where you know, or suspect, that operator& is provided by a class but you need to be really sure that you get the actual address (which is unlikely for an overloaded operator& or why else would it be overloaded in the first place?), use addressof. Summary There are not many potent arguments for overloading operator&, [7] but because it is possible, some people do it anyway. When writing code that relies on retrieving the actual address of objects, addressof can help by ensuring that the real address is returned. When writing generic code, there is no way of telling which types will be operated upon, so if the address of parameterized types needs to be taken, use addressof. [7] Custom hardware device drivers notwithstanding. Use addressof when you must retrieve the actual address of an object, regardless of the semantics for operator&. enable_if Header: "boost/utility/enable_if.hpp" Sometimes, we wish to control whether a certain function, or class template specialization, can take part in the set of available overloads/specializations for overload resolution. For example, consider an overloaded function where one version is an ordinary function taking an int argument, and the other is a templated version that requires that the argument of type T has a nested type called type. They might look like this: void some_func(int i) { std::cout << "void some_func(" << i << ")\n"; } template <typename T> void some_func(T t) { typename T::type variable_of_nested_type; std::cout << "template <typename T> void some_func(" << t << ")\n"; } Now, imagine what happens when you call some_func somewhere in your code. If the type of the argument is int, the first version is called. Assuming that the type is something other than int, the second (templated) version is called. This is fine, as long as that type has a nested type named type, but if it doesn't, this code does not compile. Is this really a problem? Well, consider what happens when another integral type is used, like short, or char, or unsigned long. #include <iostream> void some_func(int i) { std::cout << "void some_func(" << i << ")\n"; } template <typename T> void some_func(T t) { typename T::type variable_of_nested_type; std::cout << "template <typename T> void some_func(" << t << ")\n"; } int main() { int i=12; short s=12; some_func(i); some_func(s); } When compiling this program, you will get something like the following output from the frustrated compiler: enable_if_sample1.cpp: In function 'void some_func(T) [with T = short int]': enable_if_sample1.cpp:17: instantiated from here enable_if_sample1.cpp:8: error: 'short int' is not a class, struct, or union type Compilation exited abnormally with code 1 at Sat Mar 06 14:30:08 There it is. The template version of some_func has been chosen as the best overload, but the code in that version is not valid for the type short. How could we have avoided this? Well, we would have liked to only enable the template version of some_func for types with a nested type named type, and to ignore it for those without it. We can do that. The easiest way, which is not always an option in real life, is to change the return type of the template version like so: template <typename T> typename T::type* some_func(T t) { typename T::type variable_of_nested_type; std::cout << "template <typename T> void some_func(" << t << ")\n"; return 0; } If you haven't yet studied SFINAE (substitution failure is not an error), [8] chances are that you have a perplexed look on your face right now. When compiling with this update, our example compiles cleanly. The short is promoted to int, and the first version is called. The reason for this surprising behavior is that the template version of some_func isn't included in the overload resolution set anymore. It's excluded because when the compiler sees that the return type of the function requires that type be a nested type of the template type T (lots of types here), it knows that short doesn't fit the bill, so it removes the function template from the overload resolution set. This is what Daveed Vandevorde and Nicolai Josuttis have taught us to refer to as SFINAE, and it means that rather than producing a compiler error, that function simply is not considered as a valid overload for the type in question. If the type has such a nested type, though, it will be part of the overload set. [8] See [3] in the Bibliography. class some_class { public: typedef int type; }; int main() { int i=12; short s=12; some_func(i); some_func(s); some_func(some_class()); } The output when running this program is as follows: void some_func(12) void some_func(12) template <typename T> void some_func(T t) This works, but it's not a pretty sight. In this scenario, we had the luxury of playing with a void return type, which we could use for other purposes. If that hadn't been the case, we could have added another argument to the function and given it a default value. template <typename T> void some_func(T t,typename T::type* p=0) { typename T::type variable_of_nested_type; std::cout << "template <typename T> void some_func(T t)\n"; } This version al so uses SFINAE to disqualify itself from use with invalid types. The problem with both of these solutions is that they're really ugly, we have made them a part of the public interface, and they only work in some scenarios. Boost offers a much cleaner solution, which is both syntactically nicer and offers a much wider range of functionality than our earlier ad hoc solutions. Usage To use enable_if and disable_if, include "boost/utility/enable_if.hpp" . For the first example, we'll disable the second version of some_func if the type of the argument is integral. Type information such as whether a type is integral is available in another Boost library, Boost.Type_traits. The enable_if and disable_if templates both accept a predicate controlling whether to enable or disable the function, respectively. #include <iostream> #include "boost/utility/enable_if.hpp" #include "boost/type_traits.hpp" void some_func(int i) { std::cout << "void some_func(" << i << ")\n"; } template <typename T> void some_func( T t,typename boost::disable_if< boost::is_integral<T> >::type* p=0) { typename T::type variable_of_nested_type; std::cout << "template <typename T> void some_func(T t)\n"; } Although this is similar to what we did before, it's expressing something that we couldn't have done as easily using our direct approach, and this also has the advantage of documenting important information about the function in its signature. When reading this, it is clear that the function requires that the type T is not an integral type. It would be even better if we could enable it (and document it accordingly) only for types with a nested type, type, and we can do that if we use another library, Boost.Mpl. [9] Check this out: [9] Boost.Mpl is beyond the scope of this book. Visit http://www.boost.org for more information on Mpl. Also, get your hands on David Abrahams's and Aleksey Gurtovoy's book, C++ Template Metaprogramming! #include <iostream> #include "boost/utility/enable_if.hpp" #include "boost/type_traits.hpp" #include "boost/mpl/has_xxx.hpp" BOOST_MPL_HAS_XXX_TRAIT_DEF(type) void some_func(int i) { std::cout << "void some_func(" << i << ")\n"; } template <typename T> void some_func(T t, typename boost::enable_if<has_type<T> >::type* p=0) { typename T::type variable_of_nested_type; std::cout << "template <typename T> void some_func(T t)\n"; } This is very cool indeed! We are now disabling the template version of some_func when there is no nested type, type, in T, and we explicitly document that this is a requirement for the function in its signature. The trick here is to use a very nifty feature of Boost.Mpl that can test whether a certain nested type (or typedef) exists in an arbitrary type T. Using the macro invocation, BOOST_MPL_HAS_XXX_TRAIT_DEF(type), we define a new trait called has_type, which we use in the function some_func as the predicate for enable_if. If the predicate yields TRue, the function is part of the ov erload set; if it yields false, it is excluded. It's also possible to wrap the return type rather than add an extra (defaulted) argument. The equivalent of our latest and greatest some_func, but using enable_if in the return type, looks like this. template <typename T> typename boost::enable_if<has_type<T>,void>::type some_func(T t) { typename T::type variable_of_nested_type; std::cout << "template <typename T> void some_func(T t)\n"; } If you need to return the type that you need to enable or disable on, using enable_if and disable_if on the return type makes more sense than adding a defaulted argument. Also, there is a chance that someone actually provides a value instead of that default argument, which breaks the code. Sometimes, class template specializations need to be enabled or disabled, and enable_if/disable_if work for those cases too. The difference is that for class templates, we must give the primary template some special treatmentan additional template parameter. Consider a class template with a member function max that returns an int: template <typename T> class some_class { public: int max() const { std::cout << "some_class::max() for the primary template\n"; return std::numeric_limits<int>::max(); } }; Suppose we decide that for all arithmetic types (integral types and floating point types), there should be a specialization available that returns max as the maximum value that the type can hold. We'll thus need std::numeric_limits for the template type T, and we want all other types to use the primary template. To make this work, we must add a template parameter to the primary template, which has a default type of void (this means that users don't have to deal explicitly with this type). This results in the following primary template: template <typename T,typename Enable=void> class some_class { public: int max() const { std::cout << "some_class::max() for the primary template\n"; return std::numeric_limits<int>::max(); } }; We've now paved the way for providing a more specialized version, to be enabled if the type is arithmetic. That trait is available via the Boost.Type_traits library. Here's the specialization: template <typename T> class some_class<T, typename boost::enable_if<boost::is_arithmetic<T> >::type> { public: T max() const { std::cout << "some_class::max() with an arithmetic type\n"; return std::numeric_limits<T>::max(); } }; This version is only enabled when instantiated with a type that is arithmeticthat is, when the trait is_arithmetic yields true. This works because boost::enable_if<false>::type is void, matching the primary template specialization. The following program tests these templates with various types: #include <iostream> #include <string> #include <limits> #include "boost/utility/enable_if.hpp" #include "boost/type_traits.hpp" // Definition of the template some_class omitted int main() { std::cout << "Max for std::string: " << some_class<std::string>().max() << '\n'; std::cout << "Max for void: " << some_class<void>().max() << '\n'; std::cout << "Max for short: " << some_class<short>().max() << '\n'; std::cout << "Max for int: " << some_class<int>().max() << '\n'; std::cout << "Max for long: " << some_class<long>().max() << '\n'; std::cout << "Max for double: " << some_class<double>().max() << '\n'; } We'd expect the first two uses of some_class to instantiate the primary template, and the rest to instantiate the specialization for arithmetic types. Running the program shows that this is indeed the case. some_class::max() for the primary template Max for std::string: 2147483647 some_class::max() for the primary template Max for void: 2147483647 some_class::max() with an arithmetic type Max for short: 32767 some_class::max() with an arithmetic type Max for int: 2147483647 some_class::max() with an arithmetic type Max for long: 2147483647 some_class::max() with an arithmetic type Max for double: 1.79769e+308 That's all there is to it! Enabling and disabling overloaded functions and template specializations has heretofore required some tricky programming, which most who read the code would not fully understand. By using enable_if and disable_if, the solution becomes easier to code and read, and automatically captures the type requirements right in the declaration. In the preceding example, we use the template enable_if, which expects that the condition has a nested definition called value. This is true for most metaprogramming-enabled types, but it certainly isn't for integral constant expressions, for example. When there is no nested type called value, use enable_if_c instead, which expects an integral constant expression. Naively using the trait is_arithmetic and [...]... make use of SFINAE in a controlled way to enable or disable certain functions or types for overload resolution is complicated It also makes for hard-to-read code Using boost::enable_if is an elegant way of simultaneously stating that an overload is only eligible when certain conditions apply The same argument holds for disable_if, which is used to state the oppositethat an overload is not applicable... condition for some_class, like so: template class some_class::type> { public: T max() const { std::cout . scope of this book. Visit http://www.boost.org for more information on Mpl. Also, get your hands on David Abrahams's and Aleksey Gurtovoy's book, C++ Template Metaprogramming! #include. specialization for arithmetic types. Running the program shows that this is indeed the case. some_class::max() for the primary template Max for std::string: 2147483647 some_class::max() for the. "boost/utility/enable_if.hpp" . For the first example, we'll disable the second version of some_func if the type of the argument is integral. Type information such as whether a type is