[セットトップ]Effective C++学習ノート——条項03:できるだけconstを使う


         ,      ,         。

const多才多芸で、classesの外部でglobalとnamespaceの役割ドメインの定数を修飾することができます.または、ファイル、関数、またはブロックの役割ドメインでstaticとして宣言されたオブジェクトを修飾します.またclasses内部のstaticとnon-staticメンバー変数を修飾することもできます.ポインタに直面して、ポインタ自体、ポインタが指すもの、または両方がconstであることを指摘することもできます.次のコードです.
 
// useConst.cpp :              。
//
#include "stdafx.h"
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
char greeting[]="Hello";
char* p1=greeting;
const char* p2=greeting;
char* const p3=greeting;
const char* const p4 =greeting;
return 0;
} 

キーワードconstがアスタリスクの左側に表示されると、被指物が定数であることを示します.星の右側に表示されると、ポインタ自体が定数であることを示します.星の両側に現れると、被指物とポインタの両方が定数であることを示します.キーワードconstはタイプの前と後で同じ意味で書かれています.
定数は変えられないことを意味する.
またポインタを使用する場合、constは*番号の前であれば、ポインタが指すオブジェクトが定数であることを表します.たとえば、コード:
void fun1(const Widget* ptr);
void fun2( Widget const* ptr); 

STL反復器を説明する前に、C++pirmerの内容を復習します.以下のようにします.
それでは、C++PRIMER(第4版)で説明したことをお話しします.比較する
ポインタがconstオブジェクトを指す場合、ポインタで指すconst値を変更することは許されません.この特性を保証するために、c++言語はconstオブジェクトを指すポインタにもconst特性が必要です.
const double * cptr;//cptr may point to a double thatis const
ここで、cptrはdoubleタイプconstオブジェクトを指すポインタであり、constはcptr自体ではなくcptrポインタが指すオブジェクトタイプを先頭に立っている.すなわちcptr自体はconstではなく,再定義時に初期化する必要はなく,必要であればcptrに再付与することができる.別のconstオブジェクトを指します.ただし、cptrで指定したオブジェクトの値を変更することはできません.
これは間違っています*cptr=42;
1つのconstオブジェクトのアドレスを普通のものに割り当てます.constオブジェクト以外のポインタもコンパイル時のエラーを引き起こします.
STL反復器を説明する前に、C++pirmerの内容を復習します.以下のようにします.
それでは、C++PRIMER(第4版)で説明したことをお話しします.比較する
ポインタがconstオブジェクトを指す場合、ポインタで指すconst値を変更することは許されません.この特性を保証するために、c++言語はconstオブジェクトを指すポインタにもconst特性が必要です.
    const double * cptr;//cptr may point to a double thatis const

ここで、cptrはdoubleタイプconstオブジェクトを指すポインタであり、constはcptr自体ではなくcptrポインタが指すオブジェクトタイプを先頭に立っている.すなわちcptr自体はconstではなく,再定義時に初期化する必要はなく,必要であればcptrに再付与することができる.別のconstオブジェクトを指します.ただし、cptrで指定したオブジェクトの値を変更することはできません.
これは間違っています
*cptr=42;
1つのconstオブジェクトのアドレスを普通のものに割り当てます.constオブジェクト以外のポインタもコンパイル時のエラーを引き起こします.
 
#include "stdafx.h"
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
const double pi=3.14;
double* ptr=π //////   。
const double* cptr=π
return 0;
}

コンパイラ・レポートのエラーは次のとおりです.
1>d:\workspace\visual studio 2005\projects\useconst\useconst\useconst.cpp(19) : error C2440: 'initializing' : cannot convert from 'const double *__w64 ' to 'double *'

ただし、constオブジェクト以外のアドレスをconstオブジェクトを指すポインタに割り当てることができます.
今Effective C++に戻って、STL反復器の洗濯ポインタがプラスチックに基づいてタッチされていることを指摘します.したがって、反復器の役割はT*ポインタのようなもので、反復器がconstであることを宣言します.この反復器が異なるものを指してはならないことを示します.ポインタの値は変えられませんが、指す値は変えることができます.反復器が指すものを変更しないようにするにはconstが必要ですiterator:
次のコードを示します.
// useConst.cpp :              。
//
#include "stdafx.h"
#include <iostream>
#include <vector>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
vector<int> vec(10);
for(vector<int> ::iterator iter=vec.begin();iter!=vec.end();iter++)
{
*iter=10;
}
for(const vector<int> ::iterator iter=vec.begin();iter!=vec.end();iter++)//////c  ,iter const,    iter++
{
*iter=10;
}
for (vector<int> ::const_iterator citer=vec.begin();citer!=vec.end();citer++)////citer++   。。。
{
*citer=10;///////  ,    。
}
return 0;
}
      : 
1>d:\workspace\visual studio 2005\projects\useconst\useconst\useconst.cpp(18) : error C2678: binary '++' : no operator found which takes a left-hand operand of type 'const std::_Vector_iterator<_Ty,_Alloc>' (or there is no acceptable conversion)
1>d:\workspace\visual studio 2005\projects\useconst\useconst\useconst.cpp(24) : error C3892: 'citer' : you cannot assign to a variable that is const

この2つの反復器const修飾のオブジェクトは異なり、iterator修飾はポインタが定数であり、ポインタ値を変更することはできません.const_iteratorがconstに修飾するのはポインタが指す対像が変えられないことです.
constの最も威力のある使い方は、関数宣言時の応用です.関数宣言式では、constは関数の戻り値、各パラメータ、関数自体(メンバー関数の場合)に関連付けられます.関数に定数値を返すと、セキュリティと効率性を放棄することなく、お客様のエラーによる予期せぬ事故を低減できます.有理数のoperator*宣言:
次のコードがあります.
 
// useConst.cpp :              。

//

#include"stdafx.h"
#include<iostream>
#include<vector>
usingnamespacestd;

classPoint
{
public:
Point():x(0.0),y(0.0){};
Point(doublem,doublen):x(m),y(n){};
inlinedoubleGetX() const { returnx; }
inlinedoubleGetY() const { returny; }
inlinedoubleSetX(intm) { x =m; }
inlinedoubleSetY(intn) { y = n; }
constfriendPointoperator-(Pointlhs,Pointrhs);
private:
doublex;
doubley;
};
constPointoperator-(Pointlhs,Pointrhs)
{
returnPoint(lhs.GetX()-rhs.GetX(),lhs.GetY()-rhs.GetY());
}
int_tmain(intargc, _TCHAR* argv[])
{
Pointpa(1,2);
Pointpb(1,2);
Pointpc(1,2);
(pa-pb)=pc;
return 0;
}

エラーは次のとおりです.
1>d:\workspace\visual studio 2005\projects\useconst\useconst\useconst.cpp(32) : error C2678: binary '=' : no operator found which takes a left-hand operand of type 'const Point' (or there is no acceptable conversion)
 
以上の暴行のように.本に書いてあるように
aとbが内蔵タイプである場合、このようなコードは直接的に合法ではありません.「良好なユーザー定義タイプ」の特徴は、組み込みタイプと無端に互換性がないことを避けるため、2つの値の積に値を割り当てる動作を許可しても意味がありません.operator*の返信値をconstと宣言することで、その「つまらない付与動作」を予防することができます.これがその理由です.
constパラメータについては、特に斬新な観念はありませんが、local constオブジェクトのように、必要に応じて使用する必要があります.パラメータまたはlocalオブジェクトを変更する必要がある場合を除き、constとして宣言します.6文字を多く打っただけなのに、「「===」を入力しようとしたが、意外に「=」にキーを押した」というような悩ましいエラーを省くのは、少し前に述べたように.
constメンバー関数
 
メンバー関数にconstを実装する目的は、メンバー関数がconstオブジェクトに使用できることを確認するためです.このようなメンバー関数の重要な2つの理由:第一に、classインタフェースを比較的に理解しやすくし、どの関数がオブジェクトの内容を変更することができて、どれがだめなのかを知ることが重要です.第二に、「constオブジェクトの操作」を可能にする.これは効率的なコードの作成に重要であり、条項20で述べたように、C++プログラムの効率を改善する根本的な方法はpass by reference-to-const方式でオブジェクトを伝達することであり、この技術は、取得したconstオブジェクトを処理するためにconstメンバー関数を使用することができることを前提としている.
多くの人は一つの事実を無視している:2つのメンバー関数は定数性(constness)だけが異なる場合、再ロードすることができる.これは重要なC++特性である.次のclassを考慮して、大きな文字を表現します.
// useConst.cpp :              。
//
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
class TextBlock {
public:
TextBlock():text(NULL){}
TextBlock(string str):text(str){}
const char& operator[](size_t position) const //operator[] for const   
{
return text[position];
}
char& operator[](size_t position) //operator[] for non-const   
{ 
return text[position]; 
}
void print(const TextBlock& ctb);
private:
string text;
};
void TextBlock::print(const TextBlock &ctb)
{
cout<<ctb[0];
}
int _tmain(int argc, _TCHAR* argv[])
{
TextBlock tb("hello");
cout<<tb[0]<<endl;
const TextBlock ctb("world");
cout<<ctb[0]<<endl;
tb.print(tb);
return 0;
}

 
operator[]を再ロードし、異なるバージョンに異なる戻りタイプを付与すれば、constとnon-const TextBlocksに異なる処理を得ることができます.次のコードエラーの原因は
std::cout << tb[0]; //      non-const TextBlock
tb[0] = 'x'; //      non-const TextBlock
std::cout << ctb[0]; //      const TextBlock
ctb[0] = 'x'; //  !   const TextBlcok 

 
また、non-const operator[]の戻りタイプはreference to charであり、charではないと思います.operator[]がcharを返すだけの場合、次の文はコンパイルできません.
tb[0]='x';
これは、関数の戻りタイプが内蔵タイプである場合、関数の戻り値を変更することは合法的ではないからです.合法的であっても、変更されたのはそのコピーであり、値そのものではありません.
}
多くの場合、メンバー関数がconstの性質を備えていなくても、コンパイラのテストに合格することができます.上のTextBlockのconst char&operator[](size_tpostion)constを例に、下のコードの動作を見てみましょう.それ自体はbitwise constinessですが、返されるchar&は内部の値を修正することができ、bitwiseの概念に完全に反しています.TextBlock str("String"); str[0] = ‘A’; また、このような場合、TextBlockは文字列の長さを返す必要があると仮定する.ReGetLengthメソッドを呼び出すと、現在の文字長が再計算されますが、constでは変更は許可されません.lengthの値.しかし、クライアントにとってこれはconstであり、内部が再計算されるかどうかにかかわらず.
class CTextBlock {
public:
 
std::size_t length() const;
private:
char* pText;
std::size_t textLength; //             。
bool lengthIsValid; //         。
};
std::size_t CTextBlock::length() const
{
if(!lengthIsValid) {
textLength = std::strlen(pText); //  ! const          textLength lengthIsValid。
lengthIsValid = true;
}
return textLength;
}

 
lengthの実装はもちろんbitwise constではありません.textLengthとlengthIsValidが変更される可能性があるからです.この2つのデータの変更はconst CTextBlockオブジェクトにとって受け入れられるが、コンパイラは同意しない.bitwise constnessを堅持していますが、どうすればいいですか?解決法は簡単である:C++のconstに関連する揺動場:mutable(可変)を利用する.mutableはnon-staticメンバー変数のbitwise constness制約を解放します.
class CTextBlock {
public:

std::size_t length() const;
private:
char* pText;
std::size_t textLength; //              ,   const     。
bool lengthIsValid;
};
std::size_t CTextBlock::length() const
{
if(!lengthIsValid) {
textLength = std::strlen(pText); //      ,
lengthIsValid = true; //     。
}
return textLength;
}

 
いくつかの点をまとめます.
1.constとして宣言すると、コンパイラが誤った使い方を検出するのに役立ちます.constは、任意の役割ドメインのオブジェクト、関数パラメータ、関数戻りタイプ、メンバー関数本体に適用することができる.
2.コンパイラはbitwise constnessを強制的に実施しますが、プログラムを書くときは「概念的な定数性」(conceptual constness)を使用する必要があります.
3.constとnon-constメンバー関数が実質的に等価な実装がある場合、non-constバージョンにconstバージョンを呼び出すことでコードの重複を避けることができる