返却値がconstの関数の使用法

[戻る]
ナオーバ 2002/11/13(水) 10:25:12
constの動作がよくわかりません。

C***Docクラスに
 int m_nTest;// メンバ変数
 const int GetTest()   { return m_nTest; } 
のメンバ変数を返す関数があります。
使用するときに
 int nData = pDoc->GetTest();
 nData = 0;
とすると、nDataが変更できてしまいます。
 const int nData = pDoc->GetTest();
 nData = 0;
とすれば変更できません。エラーが発生します

使用側でconstを付ける必要があるなら、
関数の返却値に付ける必要はないものなんでしょうか
一般的に使用側でconstを付けるものでしょうか?

つたわりにくい文章で申し訳ありませんが、よろしくお願いします
WIN2000 SP2/VC6.0 SP5/MFC使用

へたれ 2002/11/13(水) 11:46:39
関数の戻り値に const を付けるのは、主に

const int* GetTest()    { return &m_nTest; }

このような、ポインタを返す場合のようです。

>const int GetTest()   { return m_nTest; }
>int nData = pDoc->GetTest();

この場合は、右辺値が const なのであって、

const int nTest = 0;
int       nData = nTest;

このような、ただの代入になってしまうのだと思います。

YuO [E-Mail] 2002/11/13(水) 12:06:31
オブジェクトを返す場合にconstを使うことはありますね。
組み込み型の最上位に付けても無意味ですから。

> 使用するときに
>  int nData = pDoc->GetTest();
>  nData = 0;
> とすると、nDataが変更できてしまいます。

nDataには,pDoc->GetText()の戻り値のコピーが入るのであって,
pDoc->GetText()の戻り値への参照を保持しているわけではないですから。


> const int* GetTest()    { return &m_nTest; }

これはナオーバさんが書かれているのと状況が異なりますね。
というのは,const intへのポインタ型を返しているのであって,値はconstでないです。
ナオーバさんが書かれているのは,値がconstを返す場合ですよね。


とりあえず,
class foo {
public:
    foo & operator++ (void);
    const foo operator++ (int);
};
foo& foo::operator++ (void) { ... return *this; } // これは適当
const foo operator++ (int)
{
    foo temp(*this);
    ++*this;
    return temp;
}
こんな時に値を返すにも関わらずconstにします。
これは,
foo bar;
bar++ ++;
のような無意味なことを禁止するためです。
#operator++(int)がfooを返すとしても,barは一度しかインクリメントされません。
#二度目のoperator++は最初のoperator++が返したテンポラリオブジェクトをインクリメントします。

int i;
i++ ++;
は禁止されていますよね。それと同じことを行うために,値にconstを付けます。
see) Effective C++ 21項 使えるときは、必ずconstを使おう
see) More Effective C++ 項目6: インクリメント演算子とデクリメント演算子で前置形式と後置形式とを区別する
#例に挙げた話はMore Effective C++にある話を簡略化したものです。

へたれ 2002/11/13(水) 12:14:22
すいません。失礼しましたm(__)m

ナオーバ 2002/11/13(水) 13:29:10
へたれさん、YuOさん回答ありがとうございます

伝わりにくくてすみません。整理します

基本がよく理解できていない癖に、
Effective C++の 使えるときは、必ずconstを使おうに従い
constにしようとしています。
そこで、上記のコーディングをしたところ期待と違いわからなくなりました。

わからない点
 1.メンバ変数を返す関数の一般形( 値がconstを返す場合限定ではなく )
 2.返却値がconstの関数を使用する時に使用側でconstを付けるしかないのか
 3.2.で使用側でconstを付けるしかないのなら定義でする必要はあるのか
 
1.の方はへたれさんが提示してくれたポインタか参照を返すのが一般的?
 const int* GetTest()    { return &m_nTest; }
 const int& GetTest()    { return m_nTest2; }

2.下記のようにしたら変更できなくなりました
 const int *pnData = pDoc->GetTest();
 *pnData = 0;
 const int &nData = pDoc->GetTest2();
 nData = 0;
3.もしかすると、使用側でconstをつけないとエラーが出るので
 それを期待して、関数の返却値にconstをつけるの?
 
内容が少し変わってしまいましたが、この3点がわかりません。
再度、よろしくお願いします

PAI 2002/11/13(水) 13:29:46
int* const  p; // これはポインタがconst
const int*  p; // これは指してる先がconst

int&        i; // 参照は参照先をかえることはできないから参照自体は常にconst、この場
合参照先は非const
const int&  i; // 参照先がconst

参照を返さない限り、基本的に関数の戻り値は(参照を返さない限り)コピーがかえるため、
それ自体をconstにすることは(へたれさんのいうように、ただの代入になり)意味がない。
ただ、返ったオブジェクトがその場で左辺値として扱われてしまうことを避けるため、
値(コピーがかえる場合)でもconstをつけることはある。
特にYuOさんの言うように、インクリメント演算子など。

SomeClass Foo();        // こういう関数があるとする。
Foo() = SomeClass();    // Fooの結果返って来たテンポラリオブジェクトに新たなテン
ポラリオブジェクトを代入する(すごく変、かつ無意味)

const SomeClass Bar();  // こういう関数があるとする
Bar() = SomeClass();    // これがちゃんとエラーになる。


参照先、ポインタの先の型をconstにして返すことはまた別の話で、これは重要。
関数の引数もほぼ同様。

void foo( const int i );  // fooのなかでiを変えないというおまじない。
void foo( int& i )        // 参照渡し

void foo( const int* p );  // 指してる先がconst。
void foo( int * const p ); // fooのなかでpを変えないというおまじない。

PAI 2002/11/13(水) 13:39:31
かぶっちゃいました、すいません。

で、

> 1.メンバ変数を返す関数の一般形( 値がconstを返す場合限定ではなく )

値の場合、プリミティブは非const、オブジェクトはconst、
参照、ポインタの場合は用途による。いわゆるGetterはconst。

オブジェクトはいろいろあって一概には言えない。
コピーされてもかまわないときは値、コピーされて困るときは参照。
NULLを返したいときがあるとか、ヒープに作るとかがあったらポインタ。
左辺値を返したい(返したものに代入等したい)時は参照。

とか。


> 2.返却値がconstの関数を使用する時に使用側でconstを付けるしかないのか

というより、
const int& と int& 、 const int*, int* は別の型。
別の型だから、constをつける、つけないの話ではない。型に合わせる。


> 3.2.で使用側でconstを付けるしかないのなら定義でする必要はあるのか

2. に同じ、用途による。別の型なので、それぞれの利点を考える。

PAI 2002/11/13(水) 14:14:16
一応補足、

constの参照と非constの参照は別の型だが、
非constはconstへ暗黙に変換できる。(逆はできない、意味を考えていただければわかるか
と。)

なので、使用する側では、非constのものにconstをつける分には一向に構いません。


で、上の私の文章よくわからんですな。

[1]
まず、
    ・値(コピー)
    ・ポインタ
    ・参照
の3つがあり、

[2]
値、およびポインタには
    ・const
    ・非const
の区別があり、(これを「それ自体が、」とか良く表現します。)

[3]
ポインタ、参照には
    ・指してる先const
    ・指してる先非const
があります。


で、[2]の、それ自体のconst、非constは、特殊な場合を除いておまじない
みたいなものですので使用側で好きにすればいいと思います。
関数の戻りでこれを指定しても、返った直後のテンポラリオブジェクトについてしか
言及できません。直後に何かにコピーされるなどされて、たいてい意味がありません。

問題は[3]の指してる先のconst、非const、なわけです。
が、これらは全く別の型なので、用途による、としかいえません。
ですが一般的には、単純なgetterでは、全てconstにして返すべきでしょう。
operator[]等では代入を許すため、非constの参照を返します。

ナオーバ 2002/11/14(木) 09:35:02
[[解決]]
PAIさんありがとうございます。

>const int& と int& 、 const int*, int* は別の型。
>別の型だから、constをつける、つけないの話ではない。型に合わせる。
ここが理解できていませんでした。
「別の型」って考えるとわかりました。

>一般的には、単純なgetterでは、全てconstにして返すべきでしょう
そうですね。そのようにします。

まだ完全に理解できていませんが、
提示した1.2.3.がわかりましたので解決にします。
(何がわからないのかがわからない状態では質問できないので)

何回も読み返しながら理解を深めようと思います
ありがとうございました。

古いスレッドには追加発言できません。