Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
53
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

SomeStruct::c = 5;

//Accessing static member variable c in struct SomeStruct, through var and p. var.a = var.c;

var.b = p->c;

//Calling a static member function.

SomeStruct::bar(); var.bar(); p->bar();

Background

The -> operator is needed because the member access operator . has precedence over the dereferencing operator

*.

One would expect that *p.a would dereference p (resulting in a reference to the object p is pointing to) and then accessing its member a. But in fact, it tries to access the member a of p and then dereference it. I.e. *p.a is equivalent to *(p.a). In the example above, this would result in a compiler error because of two facts: First, p is a pointer and does not have a member a. Second, a is an integer and, thus, can't be dereferenced.

The uncommonly used solution to this problem would be to explicitly control the precedence: (*p).a

Instead, the -> operator is almost always used. It is a short-hand for first dereferencing the pointer and then accessing it. I.e. (*p).a is exactly the same as p->a.

The :: operator is the scope operator, used in the same manner as accessing a member of a namespace. This is because a static class member is considered to be in that class' scope, but isn't considered a member of instances of that class. The use of normal . and -> is also allowed for static members, despite them not being instance members, for historical reasons; this is of use for writing generic code in templates, as the caller doesn't need to be concerned with whether a given member function is static or non-static.

Section 34.9: Member Types and Aliases

A class or struct can also define member type aliases, which are type aliases contained within, and treated as members of, the class itself.

struct IHaveATypedef { typedef int MyTypedef;

};

struct IHaveATemplateTypedef { template<typename T>

using MyTemplateTypedef = std::vector<T>;

};

Like static members, these typedefs are accessed using the scope operator, ::.

IHaveATypedef::MyTypedef i = 5; // i is an int.

IHaveATemplateTypedef::MyTemplateTypedef<int> v; // v is a std::vector<int>.

As with normal type aliases, each member type alias is allowed to refer to any type defined or aliased before, but not after, its definition. Likewise, a typedef outside the class definition can refer to any accessible typedefs within the class definition, provided it comes after the class definition.

template<typename T> struct Helper {

GoalKicker.com – C++ Notes for Professionals

178

T get() const { return static_cast<T>(42); }

};

struct IHaveTypedefs {

// typedef MyTypedef NonLinearTypedef; // Error if uncommented. typedef int MyTypedef;

typedef Helper<MyTypedef> MyTypedefHelper;

};

IHaveTypedefs::MyTypedef i; // x_i is an int. IHaveTypedefs::MyTypedefHelper hi; // x_hi is a Helper<int>.

typedef IHaveTypedefs::MyTypedef TypedefBeFree; TypedefBeFree ii; // ii is an int.

Member type aliases can be declared with any access level, and will respect the appropriate access modifier.

class TypedefAccessLevels { typedef int PrvInt;

protected:

typedef int ProInt;

public:

typedef int PubInt;

};

TypedefAccessLevels::PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private. TypedefAccessLevels::ProInt pro_i; // Error: TypedefAccessLevels::ProInt is protected. TypedefAccessLevels::PubInt pub_i; // Good.

class Derived : public TypedefAccessLevels {

PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private. ProInt pro_i; // Good.

PubInt pub_i; // Good.

};

This can be used to provide a level of abstraction, allowing a class' designer to change its internal workings without breaking code that relies on it.

class Something {

friend class SomeComplexType;

short s;

// ...

public:

typedef SomeComplexType MyHelper;

MyHelper get_helper() const { return MyHelper(8, s, 19.5, "shoe", false); }

// ...

};

// ...

Something s;

Something::MyHelper hlp = s.get_helper();

In this situation, if the helper class is changed from SomeComplexType to some other type, only the typedef and the

GoalKicker.com – C++ Notes for Professionals

179

friend declaration would need to be modified; as long as the helper class provides the same functionality, any code that uses it as Something::MyHelper instead of specifying it by name will usually still work without any modifications. In this manner, we minimise the amount of code that needs to be modified when the underlying implementation is changed, such that the type name only needs to be changed in one location.

This can also be combined with decltype, if one so desires.

class SomethingElse {

AnotherComplexType<bool, int, SomeThirdClass> helper;

public:

typedef decltype(helper) MyHelper;

private: InternalVariable<MyHelper> ivh;

// ...

public:

MyHelper& get_helper() const { return helper; }

// ...

};

In this situation, changing the implementation of SomethingElse::helper will automatically change the typedef for us, due to decltype. This minimises the number of modifications necessary when we want to change helper, which minimises the risk of human error.

As with everything, however, this can be taken too far. If the typename is only used once or twice internally and zero times externally, for example, there's no need to provide an alias for it. If it's used hundreds or thousands of times throughout a project, or if it has a long enough name, then it can be useful to provide it as a typedef instead of always using it in absolute terms. One must balance forwards compatibility and convenience with the amount of unnecessary noise created.

This can also be used with template classes, to provide access to the template parameters from outside the class.

template<typename T> class SomeClass {

// ...

public:

typedef T MyParam;

MyParam getParam() { return static_cast<T>(42); }

};

template<typename T>

typename T::MyParam some_func(T& t) { return t.getParam();

}

SomeClass<int> si;

int i = some_func(si);

This is commonly used with containers, which will usually provide their element type, and other helper types, as member type aliases. Most of the containers in the C++ standard library, for example, provide the following 12 helper types, along with any other special types they might need.

template<typename T>

GoalKicker.com – C++ Notes for Professionals

180

class SomeContainer {

// ...

public:

// Let's provide the same helper types as most standard containers.

typedef T

value_type;

typedef std::allocator<value_type>

allocator_type;

typedef value_type&

reference;

typedef const value_type&

const_reference;

typedef value_type*

pointer;

typedef const value_type*

const_pointer;

typedef MyIterator<value_type>

iterator;

typedef MyConstIterator<value_type>

const_iterator;

typedef std::reverse_iterator<iterator>

reverse_iterator;

typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

typedef

size_t

size_type;

typedef

ptrdiff_t

difference_type;

};

Prior to C++11, it was also commonly used to provide a "template typedef" of sorts, as the feature wasn't yet available; these have become a bit less common with the introduction of alias templates, but are still useful in some situations (and are combined with alias templates in other situations, which can be very useful for obtaining individual components of a complex type such as a function pointer). They commonly use the name type for their type alias.

template<typename T> struct TemplateTypedef {

typedef T type;

}

TemplateTypedef<int>::type i; // i is an int.

This was often used with types with multiple template parameters, to provide an alias that defines one or more of the parameters.

template<typename T, size_t SZ, size_t D> class Array { /* ... */ };

template<typename T, size_t SZ> struct OneDArray {

typedef Array<T, SZ, 1> type;

};

template<typename T, size_t SZ> struct TwoDArray {

typedef Array<T, SZ, 2> type;

};

template<typename T> struct MonoDisplayLine {

typedef Array<T, 80, 1> type;

};

OneDArray<int, 3>::type

arr1i; // arr1i is an Array<int, 3, 1>.

TwoDArray<short, 5>::type

arr2s;

//

arr2s

is an Array<short, 5, 2>.

MonoDisplayLine<char>::type arr3c;

//

arr3c

is an Array<char, 80, 1>.

GoalKicker.com – C++ Notes for Professionals

181