2007 05 Mechanizm koncepcji w języku C nowe oblicze szablonów [Inzynieria Oprogramowania]

background image

38

Inżynieria

oprogramowania

www.sdjournal.org

Software Developer’s Journal 05/2007

Mechanizm koncepcji w języku C++:

nowe oblicze szablonów

O

języku C++ można pisać wiele. Przez jed-

nych programistów kochany, przez innych

znienawidzony – jednakże, co tu kryć –

trudno potraktować go obojętnie. C++ postrzegane

nowocześnie to już nie tylko C rozszerzone o obiek-

towość. To potężne narzędzie do budowania gene-

rycznych bibliotek i wysokowydajnych rozwiązań ty-

pu DSEL (ang. Domain Specific Embedded Langu-
ages
), w których, dzięki technikom metaprogramo-

wania działającym na etapie kompilacji, można budo-

wać zaawansowane mechanizmy modelowania abs-

trakcji, nie płacąc wysokiej ceny w postaci opóźnień

w czasie wykonania programu. Niestety – ceną za

tę potęgę jest złożoność. Wystarczy spojrzeć w kod

źródłowy dowolnej biblioteki korzystającej ze wspo-

mnianych mechanizmów (chociażby Boost.MPL lub

Boost.Spirit). Przeciętnemu programiście C++ trud-

no zrozumieć zawarty tam kod, a jeszcze trudniej –

tworzyć własne rozwiązania tego rodzaju. Gdzie le-

ży przyczyna? Wydaje się, że niektóre z właściwo-

ści języka przypadkowo okazały się bardziej potężne

niż spodziewali się jego twórcy. Mowa tu oczywiście

o szablonach (ang. templates). Niemalże wszystkie

nowoczesne techniki C++ bazują na skomplikowa-

nych sztuczkach syntaktycznych związanych z sza-

blonami, odkrywanych sukcesywnie przez ostatnie

lata. W niniejszym artykule opisuję mechanizm kon-

cepcji w C++: rozszerzenie języka, które ma być re-

medium na wspomniane wyżej problemy. Czytelni-

ków, którzy pragną poznać nowe oblicze szablonów

w języku C++ zapraszam do dalszej lektury.

„Jest super, jest super,

więc o co Ci chodzi?”

W ciągu ostatnich lat C++ rozwinęło skrzydła, ewo-

luując w kierunku, którego nie przewidywali chyba

nawet jego pierwotni twórcy. Można śmiało stwier-

dzić, że to właśnie dzięki C++ programowanie ge-

neryczne trafiło pod strzechy. Ta ważna technika

stała się motorem do budowy wysoce efektywnych

i jednocześnie świetnie nadających się do wielo-

krotnego użycia bibliotek. Prekursorem tego rodza-

ju rozwiązań jest oczywiście biblioteka STL, wcho-

dząca zresztą obecnie w skład biblioteki standar-

dowej języka C++. W zasadzie historia z programo-

waniem generycznym zaczęła się dla C++ w mo-

mencie wprowadzenia do tego języka nowego roz-

szerzenia w postaci szablonów (ang. templates).

Mechanizm ten, pozwalający łatwo tworzyć rodzi-

ny klas i funkcji sparametryzowanych typami, oka-

zał się przysłowiowym strzałem w dziesiątkę. Z pa-

radygmatu programowania generycznego wyewo-

luowały inne techniki: programowanie generatyw-

ne (ang. generative programming) oraz metapro-

gramowanie bazujące na szablonach (ang. tem-
plate metaprogramming
). Programiści zaczęli two-

rzyć na bazie szablonów wysokowydajne rozwią-

zania klasy DSEL (ang, Domain Specific Embed-

ded Languages). Wydawałoby się, że lepiej już

być nie może. A jednak nie obyło się bez zgrzytów.

Aż chciałoby się zacytować słowa piosenki zespo-

łu T.Love (patrz: tytuł niniejszego podpunktu). Nie-

stety, potęga i elastyczność, oferowane przez sza-

blony języka C++ okupione zostały wysoką ceną

w postaci złożoności. W rezultacie, zarówno pro-

jektowanie jak i implementacja bibliotek bazują-

cych na wymienionych wcześniej paradygmatach,

okazały się zadaniami bardzo trudnymi. Powodów

takiego stanu rzeczy wymieniać można by wiele,

jednakże za główną przyczynę całego zamiesza-

nia można uznać pewną decyzję projektową pod-

Rafał Kocisz

Autor pracuje na stanowisku Starszego Specjalisty ds.
Oprogramowania w firmie BLStream (http://www.blstre-
am.com
) oraz odbywa studia doktoranckie na Wy-
dziale Informatyki Politechniki Szczecińskiej (http://
www.wi.ps.pl
). Centrum zainteresowań zawodowych au-
tora stanowią technologie mobilne, przetwarzanie rów-
noległe oraz języki programowania w ujęciu ogólnym.
Kontakt z autorem: rafal.kocisz@gmail.com

STLFilt kontra

komunikaty o błędach w C++

Nieczytelne błędy pojawiające się przy kompilacji biblio-
tek generycznych języka C++ mogą przyprawić o prawdzi-
wy ból głowy. Dla Leora Zolmana problem ten musiał być
szczególnie palący, tak że w końcu napisał on w języku
Perl narzędzie o nazwie STLFilt. Rozwiązanie to działa bar-
dzo prosto: na swoim wejściu pobiera komunikat o błędzie
wygenerowany przez kompilator, odsiewa śmieci i na wyj-
ściu podaje odchudzony komunikat o błędzie. Ze względu
na bardzo niestabilne warunki wejściowe (kompilatory ewo-
luują cały czas) STLFilt jest nieustannie modyfikowany. Ak-
tualnie narzędzie oferuje wsparcie dla następujących kom-
pilatorów: Comeau C++, gcc 2.95.x/3.x, DJGPP, MSVC++
6/7.x/8.x, Metrowerks CodeWarrior Pro 7/8, Borland C++ /
C++Builder, Intel C++ 7/8, EDG Front End (Generic) oraz
Digital Mars C++. STLFilt można pobrać za darmo ze stro-
ny internetowej firmy DBSoftware (której założycielem jest
właśnie Leor Zolman), pod adresem http://www.bdsoft.com/
tools/stlfilt.html
.

background image

Koncepcje w C++

39

www.sdjournal.org

Software Developer’s Journal 05/2007

jętą przy wstępnym określaniu specyfikacji szablonów. Otóż

kłopot w tym, że wspomniana konstrukcja języka nie posia-

da formalnie zdefiniowanego mechanizmu nakładania ogra-

niczeń na własne parametry (typy). Ograniczenia takowe

istnieją, aczkolwiek bazują one na pewnych konwencjach,

które można zweryfikować dopiero w późnej fazie kompi-

lacji, czyli – chciałoby się rzecz – po rybach. Dla porówna-

nia, w przypadku zwyczajnych klas, sytuacja taka jest abso-

lutnie niedopuszczalna. Przykładowo, nie możemy wywołać

na obiekcie metody, która nie jest zdefiniowana w specyfi-

kacji klasy opisującej wspomniany obiekt. Kompilator powia-

domi nas o tym problemie w bardzo wczesnej fazie swojej

pracy (Listing 1). W przypadku szablonów, sprawdzanie po-

prawności wykonywane jest dopiero w momencie tworzenia

instancji metody szablonu klasy, bądź funkcji szablonowej.

Opisana sytuacja zaprezentowana jest na Listingu 2. Na-

leży zauważyć, że gdyby usunąć ostatnią linię w ciele funk-

cji

main

, to przedstawiony fragment kodu skompilowałby się

bez żadnego problemu. W przedstawionym przykładzie bar-

dzo łatwo zlokalizować i usunąć błąd. Niestety, pisząc dużą

bibliotekę sprawa zaczyna się komplikować, gdyż programi-

sta fizycznie może nie być w stanie wymusić tworzenia in-

stancji w celu zweryfikowania poprawności. Kolejny poważ-

ny problem, to opis błędów generowanych przez kompilator:

zmora programistów pracujących z nowoczesnymi bibliote-

kami C++. Kłopot jest naprawdę ważki – moim zdaniem to

właśnie on powoduje, iż wielu początkujących informatyków

zraża się do języka C++. Wyobraźmy sobie bowiem mło-

dego adepta trudnej sztuki programowania, który próbuje

skompilować fragment kodu pokazany na Listingu 3. Listing

4 przedstawia wynikowy komunikat o błędzie wyprodukowa-

ny przez kompilator C++ Visual Studio .NET 7, zaś Listing 5

pokazuje komunikat zwrócony przez g++.

Koszmar, nieprawdaż? Dla wprawnych oczu, błąd jest

oczywisty – przekazujemy do funkcji

std::sort

parę itera-

torów, które nie spełniają wymagań narzuconych przez tę

funkcję. Problem w tym, że

sort

pracuje jedynie z iteratora-

mi o dostępie bezpośrednim (ang. random access), zaś lista

posiada iteratory oferujące dostęp sekwencyjny. Niestety

pojęcia w rodzaju iterator o dostępie bezpośrednim czy te-
rator o dostępie sekwencyjnym
nie mogą być wyrażone bez-

pośrednio w kodzie C++. Są to jedynie pewne konwencje

Rysunek 1.

Rodowód języka C++

�����������

���

������

������

���

�����������������

����������������

�����

�����������������

�����

�����������������

������

��������

���

��

���

background image

40

Inżynieria

oprogramowania

www.sdjournal.org

Software Developer’s Journal 05/2007

opisane w standardzie języka bądź w specyfikacji danej bi-

blioteki. Zachęcam Czytelników, aby postarali wyobrazić so-

bie jak wyglądają komunikaty o błędach w bardziej skompli-

kowanych bibliotekach generycznych. Kiedyś próbowałem

pożenić bibliotekę Boost.Lambda z biblioteką Boost.Smart_

ptr. W końcu mi się to udało, ale zrozumienie istoty popeł-

nianego błędu na podstawie komunikatów kompilatora zaję-

ło mi kilka godzin. Na końcu okazało się, że błąd był dość

banalny. Jeśli do tego wszystkiego weźmiemy pod uwagę

zjawisko określane potocznie jako magia kompilatorów (ang.
compiler magic) (tzn. identyczny kod kompiluje się świetnie

przy użyciu narzędzia X i generuje 3 ekrany błędów przy

próbie kompilacji narzędziem Y) to otrzymamy pełny ob-

raz problemu. Powstaje pytanie – dlaczego komunikaty o

błędach nie mogą być bardziej przyjazne dla programisty?

Przyczyna tkwi we wspomnianym wcześniej błędzie pro-

jektowym. Zasada jest prosta: im wcześniej da się wyłapać

błąd kompilacji, tym bardziej ogólny i kompaktowy będzie

komunikat na jego temat. W przypadku szablonów błędy

wyłapywane są bardzo późno i kompilator – chcąc być po-

mocny – wypisuje wszystko, co na temat danego problemu

wie. Czytelników, którzy chcieliby dowiedzieć się jak (przy-

najmniej częściowo) poradzić sobie z problemem nieczytel-

nych komunikatów o błędach w języku C++ zapraszam do

ramki STLFilt kontra komunikaty o błędach w C++. W kolej-

nym podpunkcie opowiem o grupie ludzi, którzy postanowi-

li zmienić istniejący – nieciekawy – stan rzeczy, naprawiając

opisywany problem definitywnie – u samego źródła.

Komitet standaryzacyjny

nadciąga z odsieczą!

Problem przedstawiony w poprzednim podpunkcie oka-

zał się na tyle poważny, że za jego rozwiązanie zabrali się

członkowie komitetu standaryzacyjnego języka C++, z sa-

mym Bjarne Stroustrupem na czele. Po wstępnych anali-

zach problem okazał się znacznie bardziej złożony i wielo-

wymiarowy, niż sądzono na początku. W rezultacie podjęto

bardzo odważną decyzję: rozszerzenie składni języka. I tak,

w nowej odsłonie C++ (roboczo oznaczanej jako C++0x lub

C++09) otrzymamy nowy mechanizm, który docelowo ma

stanowić remedium na kłopoty z szablonami. Mechanizm

ten nazwano koncepcjami (ang. concepts). Gdyby chcieć

jednym zdaniem scharakteryzować tę ideę, można by napi-

sać, że koncepcje to podzbiór składni języka służący do na-

kładania ograniczeń na typy w szablonach C++. W prakty-

ce oznacza to, że język będzie rozszerzony o elementy po-

zwalające określić zbiór wymagań odnośnie zadanego typu.

Dzięki temu takie abstrakcje jak iterator o dostępie bezpo-
średnim
będzie można wyrazić bezpośrednio w C++, a nie

– jak dotąd, jedynie w postaci umownej specyfikacji. Z kolei

kompilator mając z góry określony meta-model typu, będzie

w stanie już na etapie odwołania się do szablonu porów-

nać go z przekazanym argumentem. Gwoli ścisłości nale-

ży przypomnieć, że podobne zagadnienie (nakładanie ogra-

niczeń na typy będące parametrami szablonów) było rozpa-

Listing 1.

Wykrywanie błędów przy kompilacji klasy

class

foo

{

public

:

void

f1

()

const

;

}

;

class

bar

{

public

:

void

f2

(

const

foo

&

a_foo

)

{

a_foo

.

f1

();

// W porządku.

a_foo

.

f2

();

// Błąd we wczesnej fazie kompilacji!

}

}

;

Listing 2.

Wykrywanie błędów przy kompilacji szablonu

klasy

class

foo

{

public

:

void

f1

()

const

;

}

;

template

<

class

Foo

>

class

bar

{

public

:

void

f2

(

const

Foo

&

a_foo

)

{

a_foo

.

f1

();

// W porządku.

a_foo

.

f2

();

// W porządku! Nie wiemy na tym etapie

// jakie składowe ma typ Foo...

}

void

f3

()

{

}

}

;

int

main

()

{

bar

<

foo

>

b

;

// W porządku.

foo

f

;

b

.

f3

();

// W porządku.

b

.

f2

(

f

);

// Dopiero tutaj mamy błąd!

}

Listing 3.

Drobny błąd

#include

<algorithm>

#include

<list>

int

main

()

{

std

::

list

<

int

>

l

;

std

::

sort

(

l

.

begin

()

,

l

.

end

());

// ...

}

background image

Koncepcje w C++

41

www.sdjournal.org

Software Developer’s Journal 05/2007

Listing 4.

VC++: komunikat o błędzie

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

list

(

898

)

:

while

compiling

class

-

template

member

function

'

std

:

:

list

<

_Ty

>::

_Nodeptr

std

::

list

<

_Ty

>::

_Buynode

(

void

)

'

with

[

_Ty

=

int

]

Test

.

cpp

(

6

)

:

see

reference

to

class

template

instantiation

'

std

::

list

<

_Ty

>

'

being

compiled

with

[

_Ty

=

int

]

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

xstring

(

1453

)

:

warning

C4530

:

C

++

exception

handler

used

,

but

unwind

semantics

are

not

enabled

.

Specify

/

EHsc

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

xstring

(

1444

)

:

while

compiling

class

-

template

member

function

'

void

std

::

basic_string

<

_

Elem

,

_Traits

,

_Ax

>::

_Copy

(

std

::

basic_string

<

_Elem

,

_Traits

,

_Ax

>::

size_type

,

std

::

basic_string

<

_Elem

,

_

Traits

,

_Ax

>::

size_type

)

'

with

[

_Elem

=

char

,

_Traits

=

std

::

char_traits

<

char

>

,

_Ax

=

std

::

allocator

<

char

>

]

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

stdexcept

(

39

)

:

see

reference

to

class

template

instantiation

'

std

:

:

basic_string

<

_Elem

,

_Traits

,

_Ax

>

'

being

compiled

with

[

_Elem

=

char

,

_Traits

=

std

::

char_traits

<

char

>

,

_Ax

=

std

::

allocator

<

char

>

]

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

algorithm

(

1795

)

:

error

C2784

:

'

reverse_iterator

<

_RanIt

>::

difference_type

std

::

operator

-(

const

std

::

reverse_iterator

<

_RanIt

>

&

,

const

std

::

reverse_iterator

<

_RanIt

>

&)

'

:

could

not

deduce

template

argument

for

'

const

std

::

reverse_iterator

<

_

RanIt

>

&

'

from

'

std

::

list

<

_Ty

>::

iterator

'

with

[

_Ty

=

int

]

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

xutility

(

634

)

:

see

declaration

of

'

std

::

operator

`

-

''

Test

.

cpp

(

7

)

:

see

reference

to

function

template

instantiation

'

void

std

::

sort

<

std

::

list

<

_Ty

>::

iterator

>(

_RanIt

,

_RanIt

)

'

being

compiled

with

[

_Ty

=

int

,

_RanIt

=

std

::

list

<

int

>::

iterator

]

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

algorithm

(

1795

)

:

error

C2784

:

'

reverse_iterator

<

_RanIt

>::

difference_type

std

::

operator

-(

const

std

::

reverse_iterator

<

_RanIt

>

&

,

const

std

::

reverse_iterator

<

_RanIt

>

&)

'

:

could

not

deduce

template

argument

for

'

const

std

::

reverse_iterator

<

_

RanIt

>

&

'

from

'

std

::

list

<

_Ty

>::

iterator

'

with

[

_Ty

=

int

]

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

xutility

(

634

)

:

see

declaration

of

'

std

::

operator

`

-

''

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

algorithm

(

1795

)

:

error

C2784

:

'

reverse_iterator

<

_RanIt

>::

difference_type

std

::

operator

-(

const

std

::

reverse_iterator

<

_RanIt

>

&

,

const

std

::

reverse_iterator

<

_RanIt

>

&)

'

:

could

not

deduce

template

argument

for

'

const

std

::

reverse_iterator

<

_

RanIt

>

&

'

from

'

std

::

list

<

_Ty

>::

iterator

'

with

[

_Ty

=

int

]

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

xutility

(

634

)

:

see

declaration

of

'

std

::

operator

`

-

''

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

algorithm

(

1795

)

:

error

C2784

:

'

reverse_iterator

<

_RanIt

>::

difference_type

std

::

operator

-(

const

std

::

reverse_iterator

<

_RanIt

>

&

,

const

std

::

reverse_iterator

<

_RanIt

>

&)

'

:

could

not

deduce

template

argument

for

'

const

std

::

reverse_iterator

<

_

background image

42

Inżynieria

oprogramowania

www.sdjournal.org

Software Developer’s Journal 05/2007

Listing 5.

g++: komunikat o błędzie

Test

.

cpp

:

8

:

2

:

warning

:

no

newline

at

end

of

file

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

In

function

`

void

std

::

sort

(

_RandomAccessIterator

,

_

RandomAccessIterator

)

[

with

_RandomAccessIterator

=

std

::

_List_iterator

<

int

>]

'

:

Test

.

cpp

:

7

:

instantiated

from

here

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

2553

:

error

:

no

match

for

'

operator

-

'

in

'

__last

-

__first

'

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

In

function

`

void

std

::

__final_insertion_sort

(

_

RandomAccessIterator

,

_RandomAccessIterator

)

[

with

_RandomAccessIterator

=

std

::

_List_iter

ator

<

int

>]

'

:

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

2554

:

instantiated

from

`

void

std

::

sort

(

_RandomAccessIterator

,

_RandomAccessIterator

)

[

with

_RandomAccessIterator

=

std

::

_List_iterator

<

int

>]

'

Test

.

cpp

:

7

:

instantiated

from

here

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

2197

:

error

:

no

match

for

'

operator

-

'

in

'

__last

-

__first

'

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

2199

:

error

:

no

match

for

'

operator

+

'

in

'

__first

+

_S_threshold

'

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

2200

:

error

:

no

match

for

'

operator

+

'

in

'

__first

+

_S_threshold

'

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

In

function

`

void

std

::

__insertion_sort

(

_RandomAccessIterator

,

_RandomAccessIterator

)

[

with

_RandomAccessIterator

=

std

::

_List_iterator

<

i

nt

>]

'

:

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

2203

:

instantiated

from

`

void

std

::

__final_insertion_sort

(

_

RandomAccessIterator

,

_RandomAccessIterator

)

[

with

_RandomAccessIterator

=

st

d

::

_List_iterator

<

int

>]

'

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

2554

:

instantiated

from

`

void

std

::

sort

(

_RandomAccessIterator

,

_RandomAccessIterator

)

[

with

_RandomAccessIterator

=

std

::

_List_iterator

<

int

>]

'

Test

.

cpp

:

7

:

instantiated

from

here

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

2113

:

error

:

no

match

for

'

operator

+

'

in

'

__first

+

1'

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

2203

:

instantiated

from

`

void

std

::

__final_insertion_sort

(

_

RandomAccessIterator

,

_RandomAccessIterator

)

[

with

_RandomAccessIterator

=

st

d

::

_List_iterator

<

int

>]

'

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

2554

:

instantiated

from

`

void

std

::

sort

(

_RandomAccessIterator

,

_RandomAccessIterator

)

[

with

_RandomAccessIterator

=

std

::

_List_iterator

<

int

>]

'

Test

.

cpp

:

7

:

instantiated

from

here

/

usr

/

lib

/

gcc

/

i686

-

pc

-

cygwin

/3.4.4/

include

/

c

++

/

bits

/

stl_algo

.

h

:

2119

:

error

:

no

match

for

'

operator

+

'

in

'

__i

+

1'

Listing 4 cd.

VC++: komunikat o błędzie

RanIt

>

&

'

from

'

std

::

list

<

_Ty

>::

iterator

'

with

[

_Ty

=

int

]

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

xutility

(

634

)

:

see

declaration

of

'

std

::

operator

`

-

''

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

algorithm

(

1795

)

:

error

C2676

:

binary

'-'

:

'

std

::

list

<

_Ty

>:

:

iterator

'

does

not

define

this

operator

or

a

conversion

to

a

type

acceptable

to

the

predefined

operator

with

[

_Ty

=

int

]

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

algorithm

(

1795

)

:

error

C2780

:

'

void

std

::

_Sort

(

_RanIt

,

_

RanIt

,

_Diff

,

_Pr

)

'

:

expects

4

arguments

-

3

provided

C

:

\

Program

Files

\

Microsoft

Visual

Studio

.

NET

2003\

Vc7

\

include

\

algorithm

(

1913

)

:

see

declaration

of

'

std

::

_Sort

'

trywane już znacznie wcześniej – przy pracach nad pierw-

szą odsłoną standardu C++. Wtedy jednak, po burzliwych

dyskusjach, nie zdecydowano się na obranie tej drogi. Czy-

telników zainteresowanych aspektami historycznymi mecha-

nizmu koncepcji odsyłam do książki Projektowanie i rozwój
języka C++
autorstwa Bjarne Stroustrupa. W kontekście sy-

tuacji przedstawionej na Listingu 2, korzystając z koncep-

cji, kompilator potrafiłby już na etapie odwołania się do sza-

background image

43

Koncepcje w C++

www.sdjournal.org

Software Developer’s Journal 05/2007

blonu (w instrukcji

bar< foo > b;

) stwierdzić, że coś jest nie

tak. Po prostu mógłby porównać model typu zdefiniowa-

ny w ramach zadanej koncepcji, co pozwoliłoby mu wykryć,

że zastosowany typ do wspomnianej koncepcji nie pasuje

(w tym konkretnym przypadku brakuje składowej funkcji

f2()

).

W dalszej części artykułu pokażę, jak można tego rodzaju

koncepcję zdefiniować. W tym miejscu warto jeszcze przy-

toczyć cele, jakie postawili sobie członkowie komitetu, pra-

cujący nad propozycją wspomnianego rozszerzenia języka:

• Koncepcje powinny przede wszystkim służyć jako me-

chanizm ułatwiający pisanie generycznego kodu; cel

ten ma być uzyskany poprzez uproszczenie szablonów

i uczynienie ich bezpieczniejszymi (w sensie bezpie-
czeństwa typów
). W rezultacie użytkownik nie musiałby

stosować skomplikowanych sztuczek aby uzyskać pożą-

dane efekty.

• Szablony w swojej nowej odsłonie muszą oferować ten

sam poziom wydajności jak szablony klasyczne, jako że

efektywność jest jedną z podstawowych miar sukcesu

w przypadku generycznych bibliotek języka C++.

• Mechanizm koncepcji powinien być przede wszystkim na-

stawiony na wsparcie paradygmatu programowania gene-

rycznego, dzięki czemu C++ będzie mógł nadal domino-

wać w tej dziedzinie.

• Koncepcje muszą być kompatybilne wstecz, tak aby ist-

niejące szablony mogłyby być bez problemu kompilowane

przy pomocy nowych narzędzi.

W dalszej części artykułu pokażę, jak projektanci nowej odsło-

ny języka C++ zamierzają spełnić wszystkie te wymagania.

Koncepcje w praktyce: prosty przykład

Zanim przejdę do omówienia konkretnych właściwości języ-

ka związanych z mechanizmem koncepcji, przedstawię pro-

ste ich zastosowanie. Chciałbym w ten sposób uniknąć oma-

wiania nowych właściwości języka całkowicie na sucho.

W dalszej części artykułu przedstawię bardziej zaawanso-

wane przykłady zastosowania koncepcji. Na razie zachęcam

Czytelników do zapoznania się z Listingiem 6. Przedstawiłem

tam prosty szablon funkcji.

Gdy przyjrzymy się implementacji wspomnianego szablo-

nu, to na pierwszy rzut oka widać, że możemy użyć go z każ-

dym typem, który ma zdefiniowany operator mniejszości

(<)

zwracający wartość typu bool. Popatrzmy teraz na Listing 7.

Umieściłem tam alternatywną implementację szablonu min

korzystającą z dobrodziejstw koncepcji.

W zasadzie szablon wygląda bardzo podobnie. Podstawo-

wa (i w zasadzie jedyna różnica) polega na tym, że zamiast

słowa kluczowego typename w liście parametrów szablonu

użyliśmy identyfikatora

LessThanComparable

. Szybkie spojrze-

nie na Listing 8 powinno rozwiać wszelkie wątpliwości co do

tego identyfikatora.

W ten oto sposób zdefiniowaliśmy naszą pierwszą kon-

cepcję. Przeanalizujmy kod przedstawiony na wspomnianym

Listingu. Definicja koncepcji przypomina nieco definicję sza-

Listing 6.

Szablon funkcji min

template

<

typename

T

>

const

T

&

min

(

const

T

&

x

,

const

T

&

y

)

{

return

x

<

y

?

x

:

y

;

}

Listing 7.

Szablon funkcji min: wykorzystanie koncepcji

LessThanComparable

template

<

LessThanComparable

T

>

const

T

&

min

(

const

T

&

x

,

const

T

&

y

)

{

return

x

<

y

?

x

:

y

;

}

Listing 8.

Definicja koncepcji LessThanComparable.

concept

LessThanComparable

<

typename

T

>

{

bool

operator

<(

T

,

T

);

}

;

Listing 9.

Nowe podejście do starego problemu

concept

Fooable

{

void

f1

()

const

;

void

f2

()

const

;

}

;

class

foo

{

public

:

void

f1

()

const

;

}

;

template

<

Fooable

Foo

>

class

bar

{

public

:

void

f2

(

const

Foo

&

a_foo

)

{

a_foo

.

f1

();

// W porządku.

a_foo

.

f2

();

// Błąd będzie wyłapany właśnie

// na tym etapie!

}

void

f3

()

{

}

}

;

int

main

()

{

bar

<

foo

>

b

;

// Błąd: kompilator zauważy, że

// klasa foo nie pasuje do koncepcji

// Fooable.

}

background image

44

Inżynieria

oprogramowania

www.sdjournal.org

Software Developer’s Journal 05/2007

blonu klasy. W oczy rzuca się od razu lista parametrów i de-

klaracja operatora. Przedstawiona koncepcja reprezentuje

dokładnie to, o co nam chodziło: dowolny typ, którego obiek-

ty można porównywać przy pomocy operatora mniejszości.

Spójrzmy teraz na Listing 9. Pokazałem tam jak można przy

pomocy koncepcji rozwiać problem przedstawiony wcześniej

na Listingu 2.

Koncepcje, a nowe właściwości języka

Teraz, gdy znamy już ogólną koncepcję mechanizmu kon-

cepcji, możemy bezpiecznie przejść do pełniejszego omó-

wienia powiązanych z nim właściwości języka C++. A jest

o czym dyskutować. Aby zdać sobie sprawę ze złożoności

idei koncepcji, warto zastanowić się nad pytaniem: co mo-

że być parametrem szablonu. Odpowiedź jest prosta i za-

wiera się w dwóch słowach: każdy typ – włącznie z typa-

mi definiowanymi przez użytkownika, czy nawet innymi sza-

blonami. Do przekazywania tych ostatnich dedykowana jest

nawet specjalna składnia w postaci szablonowych parame-

trów szablonu (ang. template template parameters). Teraz

wyobraźmy sobie jak złożony musi być mechanizm języka,

którego zadaniem jest definiowanie ograniczeń na dowolne

typy (np. gdyby ktoś chciał napisać szablon, który jako pa-

rametr przyjmuje inny szablon, zawierający składową klasę

o określonych metodach). Odpowiedź nasuwa się automa-

tycznie – mechanizm taki będzie cechował się podobną zło-

żonością jak jego odpowiednik służący do definiowania ty-

pów. I tak jest w istocie. Składnia koncepcji w języku C++

opiera się na pięciu nowych słowach kluczowych: concept,
concept_map, where, axiom, oraz late_check. Pierwsze

z wymienionych słów kluczowych poznaliśmy już w po-

przednim punkcie niniejszego artykułu. Słowo to służy do

definicji koncepcji: definicji ograniczenia nakładanego na

typ w postaci listy deklaracji wymaganych składników. Na

Listingu 10 przedstawiona jest przykładowa, nieco bardziej

rozbudowana koncepcja.

Koncepcja ta opisuje rodzinę typów regularnych – czy-

li takich, które możemy konstruować bez parametrów, nisz-

czyć, kopiować i porównywać. Przedstawiona koncepcja po-

siada tylko jeden parametr. Nic nie stoi jednak na przeszko-

dzie, aby stworzyć koncepcję obsługującą klika parametrów

jednocześnie, np.:

auto concept Convertible< typename T, typename U >
{
operator U( const T& );
};

Koncepcja ta określa ograniczenie zakładające, że typ

T

mo-

że być automatycznie konwertowany do typu

U

. Aby użyć tej

koncepcji musimy skorzystać ze specjalnej formy nakładania

ograniczenia na szablon w postaci klauzuli

where

. Na Listingu

11 przedstawiłem prosty szablon korzystający ze wspomnia-

nej koncepcji.

Koncepcje posiadające wielokrotne argumenty w połącze-

niu z klauzulą

where

, która pozwala nakładać dodatkowe ogra-

niczenia na te argumenty, są potężnym narzędziem do nakła-

dania ograniczeń na typy. Wyobraźmy sobie, że budujemy

szablon klasy, który jako parametry będzie przyjmował dwa

Listing 11.

Szablon wykorzystujący koncepcję

template

<

typename

U

,

typename

T

>

where

Convertible

<

T

,

U

>

U

convert

(

const

T

&

t

)

{

return

t

;

}

Listing 12.

Zastosowanie klauzuli where

template

<

typename

T1

,

typename

T2

>

where

Regular

<

T1

>

&&

Convertible

<

T1

,

T2

>

&&

Convertible

<

T2

,

T1

>

class

my_class

{

// ...

}

;

Listing 13.

Dziedziczenie i agregacja koncepcji

concept

InputIterator

<

typename

Iter

,

typename

Value

>

{

typename

value_type

;

typename

reference

;

typename

pointer

;

typename

difference_type

;

where

Regular

<

Iter

>;

// agregacja koncepcji

where

Convertible

<

reference

type

,

value_type

>;

Value

operator

*(

const

Iter

&);

// dereferencja

Iter

&

operator

++(

Iter

&);

// pre-inkrementacja

Iter

operator

++(

Iter

&

,

int

);

// post-inkrementacja

}

;

concept

ForwardIterator

<

typename

Iter

,

typename

Value

>

:

InputIterator

<

Iter

,

Value

>

// dziedziczenie koncepcji

{

// Iterator sekwencyjny jest w sensie syntaktycznym

// identyczny jak operator wejścia.

}

;

Listing 10.

Nieco bardziej rozbudowana koncepcja

auto

concept

Regular

<

typename

T

>

{

T

::

T

();

// domyślny konstruktor

T

::

T

(

const

T

&

);

// konstruktor kopiujący

T

::

~

T

();

// destruktor

T

&

operator

=(

T

&

,

const

T

&

);

// operator przypisania

bool

operator

==(

const

T

&

,

const

T

&

);

// operator

równości

bool

operator

!=(

const

T

&

,

const

T

&

);

// operator

nierówności

void

swap

(

T

&

,

T

&

);

// zamiana

}

;

background image

45

Koncepcje w C++

www.sdjournal.org

Software Developer’s Journal 05/2007

typy, przy czym typu te muszą dać się w obie strony konwer-

tować, zaś pierwszy typ powinien być regularny. Ograniczenie

tego rodzaju możemy zrealizować stosując kod przedstawio-

ny na Listingu 12.

Proste, nieprawdaż? We wspomnianym przykładzie warto

zwrócić uwagę na sposób łączenia kilku ograniczeń w klauzuli

where przy pomocy operatora iloczynu logicznego

&&

. W cie-

le klauzuli możemy stosować również operator logicznej su-

my (

||

) i negacji (

!

). Co ciekawe, ten sam rodzaj klauzuli można

używać do nakładania ograniczeń na parametry innych kon-

cepcji. Patrząc na przedstawione powyżej przykłady jasno wi-

dać, że korzystanie z omawianego mechanizmu przypomina

nieco modelowanie typów: z mniejszych koncepcji można bu-

dować większe i bardziej złożone. W tej sytuacji aż się prosi

o odpowiednik mechanizmów agregacji i dziedziczenia typów.

Projektanci koncepcji pomyśleli i o tym: koncepcje można za-

równo agregować jak i dziedziczyć. Proste przykład obydwu

wspomnianych technik, wraz z odpowiednimi komentarzami,

przedstawiłem na Listingu 13.

Warto zauważyć, że w ciele koncepcji InputIterator

umieszczone są dodatkowo wymagania odnośnie powią-

zanych typów, które powinny być zdefiniowane w szablonie

spełniającym tą koncepcję.

Reasumując, koncepcje pozwalają w bezpośredni sposób

określić interfejs typu, który ma być parametrem szablonu.

Jednakże nie zawsze ten rodzaj definicji da się zastosować.

Spójrzmy na następujący fragment kodu:

char* p = new char[ 10 ];
// ... tu wypełniamy zawartość tablicy
char* found = std::find( p, p + 10, 'a' );
// ...

Traktujemy tutaj typ char* jako iterator przekazywany do stan-

dardowej funkcji

std::find

. Zakładając, że wspomniana funkcja

zaimplementowana jest w postaci szablonu ograniczonego za

pomocą koncepcji, to na przekazywane do niej iteratory nało-

żone są odpowiednie wymagania (

InputIterator

). Jeśli spoj-

rzymy ponownie na Listing 13 to przekonamy się, iż koncep-

cja InputIterator żąda od docelowego typu posiadania typów

zagnieżdżonych (np.

value _ type

). No i w tym momencie po-

jawia się kłopot, gdyż typ char* takowych typów nie posiada.

I co z tym fantem zrobić? W C++98 problem ten można by

rozwiązać stosując tzw. cechy typów (ang. type traits); w świe-

cie koncepcji da się uzyskać bardziej eleganckie rozwiązanie.

Pomogą nam w tym tzw. odwzorowania koncepcji (ang. con-
cept maps
), które zaprojektowano właśnie po to, aby adapto-

wać składnię istniejących typów do wymagań określonych

koncepcji, bez modyfikacji tych typów. Aby przekonać się jak

to wygląda w praktyce, spójrzmy na Listing 14.

Pierwsze z dwóch przedstawionych tam odwzorowań

koncepcji (definiowanych przy pomocy słowa kluczowego

concept _ map

) adaptuje typ char* do interfejsu InputIterator.

Listing 16.

Przeciążone implementacje funkcji

std::advance

template

<

InputIterator

Iter

>

void

advance

(

Iter

&

x

,

Iter

::

difference

type

n

)

{

while

(

n

>

0

)

{

++

x

;

--

n

;

}

}

template

<

BidirectionalIterator

Iter

>

void

advance

(

Iter

&

x

,

Iter

::

difference

type

n

)

{

if

(

n

>

0

)

while

(

n

>

0

)

{

++

x

;

--

n

;

}

else

while

(

n

<

0

)

{

--

x

;

++

n

;

}

}

template

<

RandomAccessIterator

Iter

>

void

advance

(

Iter

&

x

,

Iter

::

difference

type

n

)

{

x

+=

n

;

}

Listing 14.

Proste odwzorowania koncepcji

concept_map

InputIterator

<

char

*

>

{

typedef

char

value

type

;

typedef

char

&

reference

;

typedef

char

*

pointer

;

typedef

std

::

ptrdiff_t

difference_type

;

}

;

concept_map

InputIterator

<

int

>

{

typedef

int

value

type

;

typedef

int

reference

;

typedef

int

*

pointer

;

typedef

int

difference

type

;

int

operator

*(

int

x

)

{

return

x

;

}

}

;

Listing 15.

Koncepcja stosu i odwzorowanie adaptujące

concept

Stack

<

typename

X

>

{

typename

value

type

;

void

push

(

X

&

,

const

value

type

&);

void

pop

(

X

&);

value

type

top

(

const

X

&);

bool

empty

(

const

X

&);

}

;

template

<

typename

T

>

concept

map

Stack

<

std

::

vector

<

T

>

>

{

typedef

T

value

type

;

void

push

(

std

::

vector

<

T

>&

v

,

const

T

&

x

)

{

v

.

push_

back

(

x

);

}

void

pop

(

std

::

vector

<

T

>&

v

)

{

v

.

pop_back

();

}

T

top

(

const

std

::

vector

<

T

>&

v

)

{

return

v

.

back

();

}

bool

empty

(

const

std

::

vector

<

T

>&

v

)

{

return

v

.

empty

();

}

}

;

background image

46

Inżynieria

oprogramowania

www.sdjournal.org

Software Developer’s Journal 05/2007

Listing 17.

Koncepcje iteratorów z biblioteki standardowej języka C++

W tym przypadku w odwzorowaniu umieszczono jedynie od-

powiednie definicje typów; reszta wymaganych operacji jest

dostępna jako część syntaktyki typów wskaźnikowych wbu-

dowanej w język C++. Ciekawostkę stanowi druga część Li-

stingu, gdzie do koncepcji InputIterator adaptowany jest nie-

wskaźnikowy typ int. Aby wymaganiom wspomnianej koncep-

cji stało się zadość, to oprócz zapewnienia definicji typów,

trzeba jeszcze zdefiniować operator dereferencji. W rezultacie

moglibyśmy napisać następujący kod:

std::copy( 1, 10, std::ostream_iterator< int >( std::cout, ” ”

) );

przy czym na wyjściu otrzymalibyśmy sekwencję:
1 2 3 4 5 6 7 8 9 10

Odwzorowania koncepcji można zapisywać również w postaci

szablonów. W rzeczywistości możliwość ta połączona ze sto-

sowaniem technik adaptacji składni, stanowi prawdziwą moc

tej konstrukcji. Na Listingu 15 przedstawiona jest koncepcja

stosu (struktury danych) oraz odwzorowanie, które adaptu-

je do tej koncepcji kontener

std::vector

. Prawda, że eleganc-

kie rozwiązanie? Rozważając temat koncepcji w odniesieniu do

właściwości języka C++, należy zdać sobie sprawę, że szablo-

ny, które korzystają z omawianego mechanizmu, rządzą się tro-

chę innymi prawami niż szablony klasyczne. W rzeczywistości

można wręcz mówić o nowym rodzaju szablonów (w terminolo-

gii anglojęzycznej określanych jako constrained templates). Sza-

blony te mogą zawierać odniesienia do koncepcji w ramach listy

parametrów (Listing 7), bądź w ciele klauzuli where (Listing 11).

Najważniejsza cecha nowych szablonów związana z faktem, iż

sprawdzanie poprawności przekazanych do nich typów nastę-

puje bezpośrednio w momencie tworzenia instancji bazujących

na nich obiektów. Regułę tę można obejść jedynie przy pomocy

dedykowanego słowa kluczowego

late _ check

. Koncepcje oka-

zują się również wielce przydatne w sytuacji przeciążania sza-

blonów funkcji, w odniesieniu do ich typów szablonowych oraz

specjalizacji szablonów klas. Na Listingu 16 zaprezentowana

jest implementacja standardowej funkcji

std::advance

(przesu-

nięcie iteratora o n kroków do przodu), zrealizowana przy pomo-

cy przeciążenia bazującego na koncepcjach.

Jak widać, realizacja powyższego zadania w świecie kon-

cepcji staje się prosta niczym przeciążenie zwyczajnych funk-

cji na bazie ich parametrów. Aby uzyskać podobne rozwiąza-

nie w C++98 trzeba by zastosować mało eleganckie i niewy-

godne sztuczki programistyczne (np. idiom SFINAE lub wybór

implementacji bazujący na tagach).

Koncepcje: konsekwencje

W poprzednim podpunkcie Czytelnicy mieli okazję zapoznać

się mechanizmem koncepcji, postrzeganym przez pryzmat

nowych właściwości języka C++. W przedstawionym opisie

starałem się wybrać i przedstawić najbardziej istotne cechy

rozwiązania i z tej racji pominąłem cały szereg specyficznych

zagadnień związanych z tematem (chociażby wyjaśnienie

znaczenia słowa kluczowego axiom). Czytelników zaintere-

sowanych poznaniem formalnej specyfikacji koncepcji zapra-

szam do ramki W Sieci, gdzie proponuję cały szereg cieka-

wych materiałów. W niniejszym podpunkcie chciałbym prze-

analizować temat z innego punktu widzenia. Warto zastano-

wić się jakie konsekwencje pociągnie za sobą wprowadzenie

mechanizmu koncepcji do języka C++.

Na początek rozważmy w jaki sposób koncepcje wpłyną

na kształt biblioteki standardowej. Przede wszystkim możemy

spodziewać się nowego nagłówka:

<concepts>

. Nagłówek ten

Listing 18:

ConceptGcc: komunikat o błędzie

sort_list

.

cpp

:

In

function

int

main

()

:

sort_list

.

cpp

:

8

:

error

:

no

matching

function

for

call

to

sort

(

std

::

_List_iterator

<

int

>

,

std

::

_List_iterator

<

int

>)

.../

stl_algo

.

h

:

2835

:

note

:

candidates

are

:

void

std

::

sort

(

_Iter

,

_Iter

)

[

with

_Iter

=

std

::

_List_iterator

<

int

>]

sort_list

.

cpp

:

8

:

note

:

no

concept

map

for

requirement

std

::

MutableRandomAccessIterator

<

std

::

_List_iterator

<

int

>

>

concept

InputIterator

<

typename

Iter

>

:

CopyConstructible

<

Iter

>

,

EqualityComparable

<

Iter

>

,

Assignable

<

Iter

>

{

typename

value

type

=

Iter

::

value

type

;

typename

reference

=

Iter

::

reference

;

typename

pointer

=

Iter

::

pointer

;

SignedIntegral

difference

type

=

Iter

::

difference

type

where

Arrowable

<

pointer

,

value

type

>

&&

Convertible

<

reference

,

value

type

>;

reference

operator

(

Iter

);

pointer

operator

!

(

Iter

);

Iter

&

operator

++(

Iter

&);

typename

postincrement

result

;

postincrement

result

operator

++(

Iter

&

,

int

);

where

Dereferenceable

<

postincrement

result

,

value

type

>;

}

;

concept

ForwardIterator

<

typename

Iter

>

co

:

InputIterator

<

Iter

>

,

DefaultConstructible

<

Iter

>

{

where

Convertible

<

reference

,

const

value

type

&>;

where

Convertible

<

pointer

,

const

value

type

>;

}

;

concept

MutableForwardIterator

<

Iter

>

:

ForwardIterator

<

Iter

>

,

OutputIterator

<

Iter

>

{

where

SameType

<

reference

,

value

type

&>

&&

SameType

<

pointer

,

value

type

>;

}

;

background image

Koncepcje w C++

47

www.sdjournal.org

Software Developer’s Journal 05/2007

będzie zawierał definicje standardowych koncepcji, czyli zapi-

sane w języku C++ podstawowe wymagania odnośnie typów,

zdefiniowane (zazwyczaj w postaci tabel) w standardzie języ-

ka. Można więc spodziewać się koncepcji takich jak:

Assigna-

bl

e,

DefaultConstructible

,

LessThanComparable

,

EqualityCom-

parable

itd. Niewątpliwym wyzwaniem dla twórców biblioteki

standardowej będzie zaprojektowanie hierarchii koncepcji ite-

ratorów (na Listingu 17 przedstawiona jest jedna z możliwych

prób implementacji części tej hierarchii). W tej sytuacji biblio-

teka STL będzie przepisana od nowa i istnieje duża szansa,

że doczekamy się bardziej przyjaznych komunikatów o błę-

dach. Mając w zanadrzu mechanizm koncepcji, będzie moż-

na zaimplementować też algorytmy działające bezpośrednio

na kolekcjach (a nie – tak jak dotychczas – jedynie ma parach

iteratorów). Co ważne, standardowe koncepcje będą stanowi-

ły solidną bazę do tworzenia własnych bibliotek generycznych

– prawdopodobnie zdecydowanie bardziej intuicyjnych i prze-

nośnych niż podobne rozwiązania tworzone w C++98.

Koncepcje niewątpliwie wpłyną też na pokrewne paradyg-

maty programistyczne. Przy projektowaniu klas na bazie stra-

tegii (ang. policy based class design), koncepcje będzie moż-

na wykorzystać do definiowania interfejsów. Przy tworzeniu

rozwiązań typu DSEL, techniki metaprogramowania oparte-

go na szablonach staną się znacznie prostsze i bardziej prze-

nośne – właśnie dzięki koncepcjom. Kto wie – może pojawią

się nowe paradygmaty programowania bazujące na koncep-

cjach? Jedno już dziś pozostaje pewne: dysponując tym me-

chanizmem, C++ będzie w stanie jeszcze bardziej umocnić

swoją pozycję głównego języka wspierającego paradygmat

programowania generycznego.

Na bezrybiu

Zdaję sobie sprawę, że wielu programistów C++ po przeczy-

taniu powyższych akapitów rozmarzy się: “ach, jak dobrze by-

łoby móc skorzystać z dobrodziejstw koncepcji już dziś.”. Tym-

czasem trzeba wracać do pracy z istniejącymi kompilatorami

i bibliotekami, które, nawiasem mówiąc, zazwyczaj nie są na-

wet w pełni kompatybilne ze specyfikacją standardu C++98.

Dla zmartwionych takim stanem rzeczy programistów C++

mam dobrą wiadomość: koncepcje są dostępne już dziś. Oso-

bom zainteresowanym bezpośrednim zapoznaniem się z nową

właściwością języka polecam otwarty kompilator ConceptGCC

(patrz: ramka W Sieci), który pozwala eksperymentować

z koncepcjami w ich pełnej postaci. Kompilator ten niestety nie

do końca nadaje się do zastosowań komercyjnych (jego imple-

mentacja w momencie pisania niniejszego tekstu znajduje się

ciągle w fazie Alpha). Fakt ten wynika w dużej mierze z nie-

stabilności specyfikacji koncepcji, nad którą ciągle prowadzo-

ne są intensywne prace. Do celów edukacyjnych narzędzie

to jest jednak całkowicie wystarczające. O jego jakości niech

zaświadczy komunikat o błędzie wygenerowany przy próbie

kompilacji programu pokazanego na Listingu 3. Wspomnia-

ny komunikat można zobaczyć na Listingu 18. Proponuję aby

Czytelnicy porównali sobie zawartość tego Listingu z komuni-

katami wygenerowanymi przez kompilatory VC++ czy g++.

Osoby, które chciałyby wdrożyć ideę koncepcji w produk-

cyjnych rozwiązaniach opartych na języku C++98, powin-

ny zapoznać się z biblioteką v (BCCL). Biblioteka ta oferuje

część funkcjonalności powiązanej z mechanizmem koncep-

cji w postaci akceptowalnej przez istniejące kompilatory C++.

Na Listingu 19 można zobaczyć, jak przy pomocy BCCL moż-

na wymusić proste ograniczenie dla typu przekazywanego ja-

ko parametr szablonu klasy. Oczywiście możliwości BCCL nie

umywają się do elastyczności i potęgi koncepcji dostępnych

w postaci rozszerzenia języka, jednakże, jak mówi stare przy-

słowie: na bezrybiu i rak ryba.

Podsumowanie

W powyższym artykule przedstawiłem podstawowe infor-

macje na temat mechanizmu koncepcji, którego dołączenie

planuje się w nadchodzącej odsłonie standardu języka C++.

Koncepcje, oferujące zbiór zaawansowanych konstrukcji po-

zwalających modelować ograniczenia typów, będących pa-

rametrami w szablonach klas i funkcji, postrzegane są jako

przyszłe remedium na bolączki związane ze złożonością no-

woczesnych bibliotek generycznych, pisanych w języku C++.

Celem, który postawiłem sobie przy pisaniu niniejszego ar-

tykułu było przedstawienie mechanizmu koncepcji tak, aby

Czytelnik mógł łatwo zrozumieć ideę tego rozwiązania i jed-

nocześnie uświadomić sobie konsekwencje stosowania go w

praktyce. Kończąc, chciałbym podkreślić, że niniejszy tekst

oparty jest na roboczej specyfikacji mechanizmu koncepcji

i z tej racji nie należy traktować go w kategoriach materiału

referencyjnego. n

W Sieci

http://www.generic-programming.org/ – dużo ciekawych infor-

macji na temat programowania generycznego i koncepcji,

http://www.generic-programming.org/software/ConceptGCC/

– strona domowa projektu ConceptGCC,

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/

n2081.pdf – najświeższa specyfikacja mechanizmu koncepcji,

http://www.open-std.org/jtc1/sc22/wg21/ – strona domowa ko-

mitetu standaryzacyjnego języka C++,

http://www.boost.org/libs/concept_check/concept_check.htm

– strona domowa biblioteki Boost Concept Check..

Listing 19.

Proste zastosowanie biblioteki BCCL

template

<

class

T

>

struct

generic_library_class

{

BOOST_CLASS_REQUIRE

(

T

,

boost

,

EqualityComparableConcept

);

// ...

}

;

class

foo

{

//...

}

;

int

main

()

{

generic_library_class

<

foo

>

glc

;

// ...

return

0

;

}


Wyszukiwarka

Podobne podstrony:
2007 07 Programowanie aplikacji wielowątkowych w języku C w oparciu o wzorce projektowe [Inzynieria
2007 07 Wykorzystanie przypadków użycia do modelowania zachowania [Inzynieria Oprogramowania]
Wykład 05 - Psychospołeczne koncepcje rozwoju. Problem mora, Psychologia UJ, Psychologia rozwojowa
Nowe oblicze terroryzmu, Politologia
2007 05 02 odp
2007 05 Variadic templates w C 0x [Programowanie C C ]
Postawy heroiczne nowe oblicze heroizmu w literaturze obozowej
2007 05 14 praid 25651 Nieznany
SIMR-AN1-EGZ-2007-05-09a-rozw
Nowe oblicze biznesu(1)
2007 05 14 prawdopodobie stwo i statystykaid 25652
2007 05 R
2007.05.14 prawdopodobie stwo i statystyka

więcej podobnych podstron