ページ 11

C2662エラーについて

Posted: 2012年3月06日(火) 00:44
by MoNoQLoREATOR
このページによりますと、C2662エラーは、関数定義の際 { の前に const を書くことによって解決できるそうですが(以前もその方法で解決したことがあります)、処置後も同じエラーが出続けます。引数の const を取れば解決しそうですが、できれば const のままにしておきたいところです。

以下ソースコード、エラー文、仕様書です。
ちなみに内容は自作のvectorクラスとなっております。

ソースコード
► スポイラーを表示
エラー文
► スポイラーを表示
仕様書
► スポイラーを表示

エラーの原因が私には全くわかりません。
ご教授よろしくお願い致します。

Re: C2662エラーについて

Posted: 2012年3月06日(火) 11:23
by YuO
const型のオブジェクトに対しては,constとされた関数しか呼び出せません。
「呼び出し元の関数がconst」ではなく,「呼び出す関数がconst」です。

今回の最初のエラーについていうならば,25行目の
MoNoQLoREATOR さんが書きました:

コード:

    uint size(){ return Size; }

コード:

    uint size() const { return Size; }
でないといけません。そして,93行目の
MoNoQLoREATOR さんが書きました:

コード:

    fw::vector<X> & add(const fw::vector<X> & input) const {
は,constである必要はありません。それどころか,addはオブジェクトの状態を変更する操作ですからconst修飾してはいけません。
# const修飾された関数の呼び出しでは,少なくとも外部から見たオブジェクトの状態は変化しないことが期待されます。

なお,const付きと非const付きはオーバーロードできます。


このクラス自体にもいろいろ問題があるのですが……。
# ぱっと見ただけでも,memcpyがあるので非PODで使うには危険,resizeで無駄にコンストラクタとデストラクタが走る,atでデータ構造が壊れる可能性がある

Re: C2662エラーについて

Posted: 2012年3月06日(火) 15:38
by MoNoQLoREATOR
ありがとうございます。エラーは出なくなりました。

クラス自体に問題があるというのならば、直したいです。
たしかに、ちょいとリスキーな動きだな という自覚はありますが^^;

自覚があるのは、おかしな場所でdeleteしている点です。
「ユーザー視点において確保された容量」を超える要素にアクセスしてしまう事態が起こった場合は、範囲外アクセスエラーを出すべきだと思うわけですが、内部では「ユーザー視点において確保された容量」よりも多めに容量が確保されている状態であり、ほとんどの場合、自動で範囲外アクセスエラーが出ません。そこで、そのような事態が起こった場合は、確保した容量をdeleteしてしまうことによって強制的に範囲外アクセスエラーが出るようにしています。やはりtry~catch文を書いたほうがよいのでしょうか?

YuOさんに指摘していただいた点については疑問しか出てきません。

>>memcpyがあるので非PODで使うには危険
PODという言葉が出てきたということは、ファイルに出力したりmemcpyを用いてデータをコピーしたりする場面でのお話ですよね?要素群のデータをファイルに出力したりmemcpyを用いてコピーしたりすることについてはなんら危険性はないはずです。このクラス自体に対してそのような処理をする場合は問題が出るかもしれませんが、通常そのようなことをすることはないでしょう。

>>resizeで無駄にコンストラクタとデストラクタが走る
私にはコンストラクタやデストラクタが走っているようには見えません。

>>atでデータ構造が壊れる可能性がある
デリートしている部分のことでしょうか?容量が増える場合があるのは仕様です。


おそらく私の知識が足りないものと思われます。
ご教授よろしくお願い致します。



P.S.
少しソースコードを変更しました。57行目辺りです。
► スポイラーを表示

Re: C2662エラーについて

Posted: 2012年3月06日(火) 20:34
by MoNoQLoREATOR
>>memcpyがあるので非PODで使うには危険
>>resizeで無駄にコンストラクタとデストラクタが走る

上記2点については理解できました。
1つ目について。要素として非PODのものを指定した場合に、resize()内で行われているmemcpyが危険だという意味だったのですね。

2つ目について。デストラクタは無駄には走っていないと思いますが、コンストラクタは無駄に走っていました。for文の部分ですね。配列をnewした際は、コンストラクタの引数が指定できないため、てっきりコンストラクタが呼び出されないのかと思っていました。ちゃっかりデフォルトコンストラクタが呼び出されているのですね。


ソースコードを修正してみました。ついでに、容量を実際に拡張(新しく領域を確保し、そちらにお引越しをすることによるもの)する際の効率が良くなるようにしてみました。
► スポイラーを表示

Re: C2662エラーについて

Posted: 2012年3月06日(火) 21:05
by MoNoQLoREATOR
memcpyについて。
vectorにおいての挙動を下記のソースコードを用いて実験してみました。
► スポイラーを表示
結果は期待通りとなりました。
データを全てコピーしているので、当たり前と言えば当たり前かもしれません。
vectorの内部でどのような処理がなされているのかは私は知りませんので、期待通りの結果にならない可能性はあったわけですが・・・。
たとえ非PODなデータであっても、memcpyを用いることは危険ではないのではないかと思います。その辺りはどうなのでしょうか?

Re: C2662エラーについて

Posted: 2012年3月06日(火) 21:47
by softya(ソフト屋)
コピーコンストラクタではなくmemcpyを使うと内部情報(ポインタ等)まで複製してしまいます。

なので、こうするとvとv2が同じポインタを中途半端に共有していることが分かります。

コード:

#include <vector>
 
using namespace std;
 
void main(){
    vector<int> * v = new vector<int>[3];
    for(int i=0;i<3;i++) for(int j=0;j<5;j++) v[i].push_back(j);
    
    vector<int> * v2 = new vector<int>[3];
    
    memcpy(v2,v,sizeof(vector<int>)*3);
    for(int i=0;i<3;i++) for(int j=0;j<5;j++) v[i].push_back(j);
 
    for(int i=0;i<3;i++) for(size_t j=0;j<v[i].size();j++) printf("%d,", v[i][j]);
    printf("\n");
    for(int i=0;i<3;i++) for(size_t j=0;j<v2[i].size();j++) printf("%d,", v2[i][j]);
    printf("\n");
}

Re: C2662エラーについて

Posted: 2012年3月06日(火) 23:16
by MoNoQLoREATOR
なるほど、たしかにそうですね。
今回のソースコードでは、コピー元の領域はすぐに解放していますし、コピー後は使用しないのでこの心配はありませんね。だからmemcpyを使うことによる危険性は今回の場合ないのではないかと思うのですがどうでしょうか?

Re: C2662エラーについて

Posted: 2012年3月06日(火) 23:46
by softya(ソフト屋)
MoNoQLoREATOR さんが書きました:なるほど、たしかにそうですね。
今回のソースコードでは、コピー元の領域はすぐに解放していますし、コピー後は使用しないのでこの心配はありませんね。だからmemcpyを使うことによる危険性は今回の場合ないのではないかと思うのですがどうでしょうか?
これでも大丈夫だと思いますか?

コード:

#include <vector>
 
using namespace std;
 
void main(){
    vector<int> * v = new vector<int>[3];
    for(int i=0;i<3;i++) for(int j=0;j<5;j++) v[i].push_back(j);
    
    vector<int> * v2 = new vector<int>[3];
    
    memcpy(v2,v,sizeof(vector<int>)*3);
    
    
    for(int i=0;i<3;i++) for(size_t j=0;j<v[i].size();j++) printf("%d,", v[i][j]);
    printf("\n");
    for(int i=0;i<3;i++) for(size_t j=0;j<v2[i].size();j++) printf("%d,", v2[i][j]);
    printf("\n");
    
    delete[] v;
    vector<float> * vf = new vector<float>[3];
    for(int i=0;i<3;i++) for(int j=0;j<5;j++) vf[i].push_back(j+5);
 
    for(int i=0;i<3;i++) for(size_t j=0;j<v2[i].size();j++) printf("%d,", v2[i][j]);
    printf("\n");
}

Re: C2662エラーについて

Posted: 2012年3月07日(水) 04:31
by YuO
MoNoQLoREATOR さんが書きました:自覚があるのは、おかしな場所でdeleteしている点です。
「ユーザー視点において確保された容量」を超える要素にアクセスしてしまう事態が起こった場合は、範囲外アクセスエラーを出すべきだと思うわけですが、内部では「ユーザー視点において確保された容量」よりも多めに容量が確保されている状態であり、ほとんどの場合、自動で範囲外アクセスエラーが出ません。そこで、そのような事態が起こった場合は、確保した容量をdeleteしてしまうことによって強制的に範囲外アクセスエラーが出るようにしています。やはりtry~catch文を書いたほうがよいのでしょうか?
MoNoQLoREATOR さんが書きました:>>atでデータ構造が壊れる可能性がある
デリートしている部分のことでしょうか?容量が増える場合があるのは仕様です。
delete[]されたオブジェクトに対するアクセスは,「未定義の振る舞い」です。
おそらく,delete[]されたオブジェクトへアクセスしても,とりあえずは何事もないように動くでしょう。
で,次にnewされたあたりでオブジェクトが破壊されたように見えて,バグが見つかることになります。

範囲外アクセスを通知したいのであれば,std::vectorなどと同じく例外を明示的に発生させる必要があります。

MoNoQLoREATOR さんが書きました:>>memcpyがあるので非PODで使うには危険
PODという言葉が出てきたということは、ファイルに出力したりmemcpyを用いてデータをコピーしたりする場面でのお話ですよね?要素群のデータをファイルに出力したりmemcpyを用いてコピーしたりすることについてはなんら危険性はないはずです。このクラス自体に対してそのような処理をする場合は問題が出るかもしれませんが、通常そのようなことをすることはないでしょう。
コピーしたあと一切触らないのですか?
それであれば問題ないですが,そうではないですよね。

ポインタがあるようなクラスをmemcpyすると大抵の場合データ不整合が起きます。
# 二重deleteなんかもおきやすい。
クラスの作者はコピーコンストラクタや代入演算子のオーバーロードをしたり,場合によってはそれらを使えなくすることでデータの不整合を防いでいます。
しかし,memcpyはそれらをまったく無視するため,データがおかしくなり,思いもよらぬところで動作がおかしくなる原因となります。

確認ですが,以下のコードが未定義動作であることはちゃんと理解していますか。

コード:

#include <iostream>
#include <cstdlib>
#include <new>
class foo
{
private:
    int * p;
    friend std::ostream & operator << (std::ostream & stream, const foo & obj);
/*
    C++11未対応の場合 (e.g. VC++) は,コメント直後の2行を
    foo (const foo &);
    foo & operator = (const foo &);
    として,それぞれの本体を定義しないこと。
*/
    foo (const foo &) = delete;
    foo & operator = (const foo &) = delete;
public:
    foo () : p(new int) { }
    ~foo () { delete p; }
};

inline std::ostream & operator << (std::ostream & stream, const foo & obj)
{
    stream << &obj << " ( " << obj.p << " )";
    return stream;
}

int main ()
{
    foo * p1 = new foo();
    foo * p2 = new foo();
    std::memcpy(p2, p1, sizeof(foo));

    std::cout << *p1 << std::endl;
    std::cout << *p2 << std::endl;

    delete p1;
    delete p2;

    return 0;
}
MoNoQLoREATOR さんが書きました:>>resizeで無駄にコンストラクタとデストラクタが走る
私にはコンストラクタやデストラクタが走っているようには見えません。
MoNoQLoREATOR さんが書きました:2つ目について。デストラクタは無駄には走っていないと思いますが、コンストラクタは無駄に走っていました。for文の部分ですね。配列をnewした際は、コンストラクタの引数が指定できないため、てっきりコンストラクタが呼び出されないのかと思っていました。ちゃっかりデフォルトコンストラクタが呼び出されているのですね。
使いもしないオブジェクトのコンストラクタが走り,それに伴い削除時にデストラクタが走るのが「無駄」です。

コード:

T * p = (T *)std::malloc(sizeof(T) * new_capacity);
for (std::size_t i = 0; i < new_capacity; ++i)
{
    new (p + i) T(old_data[i]);;
}
のように,配置newを使って必要なオブジェクトだけをコピーコンストラクタで初期化し,さらに明示的にデストラクタを呼び出すことで,これらの無駄を省けます。


ちなみに,fw::vectorはVS2010sp1の環境下で257個目の要素を追加しようとした時にヒープが壊れていることが通知されました。
# PODじゃない (operator=がある) けど,intが二つとint *のみを持つだけでデストラクタのない (かつ不要な) クラスを渡した。

Re: C2662エラーについて

Posted: 2012年3月07日(水) 12:51
by MoNoQLoREATOR
>>softya(ソフト屋)さん
>>YuOさん
返信ありがとうございます。
なるほどそのようなケースも有りうるのですね。勉強になりました。
下記のようにソースコードを修正してみます。
・例外処理を加える
・memcpyを廃止。

Re: C2662エラーについて

Posted: 2012年3月07日(水) 14:14
by MoNoQLoREATOR
ソースコードは以下のようになりました。
► スポイラーを表示
ちなみに仕様に問題があったので修正しました。
► スポイラーを表示
例外処理を書いているときに疑問がわいてきたのですが、 abort()関数で強制終了させた場合にメモリ領域は解放されるのでしょうか?よくわからなかったので、とりあえず範囲外アクセスが発生した旨を伝えるメッセージボックスを表示させるだけにしておきました。

容量を増やす処理は、ご覧の通り配置newを使わずに書きましたがこれでは駄目なのでしょうか?

Re: C2662エラーについて

Posted: 2012年3月07日(水) 15:53
by YuO
MoNoQLoREATOR さんが書きました:例外処理を書いているときに疑問がわいてきたのですが、 abort()関数で強制終了させた場合にメモリ領域は解放されるのでしょうか?
標準規格の範囲外です。
現実的には,外部環境が勝手に回収すると思いますが。
MoNoQLoREATOR さんが書きました:よくわからなかったので、とりあえず範囲外アクセスが発生した旨を伝えるメッセージボックスを表示させるだけにしておきました。
とりあえずの対応であればよいですが,ユーザーへの通知は最終的なプロダクトを作成する側の役割です。
ライブラリが勝手にユーザーへ通知するのは,たいていの場合使いにくくなるだけです。
MoNoQLoREATOR さんが書きました:容量を増やす処理は、ご覧の通り配置newを使わずに書きましたがこれでは駄目なのでしょうか?
だめです。代入先にオブジェクトを構築していません。

コード:

struct bar {
    bar ()
    {
        std::cout << "constructor : " << this << std::endl;
    }
    ~bar ()
    {
        std::cout << "destructor : " << this << std::endl;
    }
    bar (const bar & other)
    {
        std::cout << "copy constructor : " << this << " from " << &other << std::endl;
    }
    bar & operator= (const bar & other)
    {
        std::cout << "operator = : " << this << " from " << &other << std::endl;
        return *this;
    }
};
というクラスをfw::vectorの要素にして,代入が構築済みのものに対してのみ行われることを確認してみてください。

Re: C2662エラーについて

Posted: 2012年3月07日(水) 19:58
by MoNoQLoREATOR
>>YuOさん
返信ありがとうございます。

コードを以下のようにしてみました。
► スポイラーを表示
配置newの場合はdeleteする必要は無いという理解でよいのでしょうか?


例外処理についてなのですが。
>>ユーザーへの通知は最終的なプロダクトを作成する側の役割です。
>>ライブラリが勝手にユーザーへ通知するのは,たいていの場合使いにくくなるだけです。
では何も言わずに強制終了させた方が良いのでしょうか?

Re: C2662エラーについて

Posted: 2012年3月07日(水) 20:41
by bitter_fox
MoNoQLoREATOR さんが書きました: 例外処理についてなのですが。
>>ユーザーへの通知は最終的なプロダクトを作成する側の役割です。
>>ライブラリが勝手にユーザーへ通知するのは,たいていの場合使いにくくなるだけです。
では何も言わずに強制終了させた方が良いのでしょうか?
例外を投げればいいんじゃないですか?

Re: C2662エラーについて

Posted: 2012年3月07日(水) 22:18
by YuO
MoNoQLoREATOR さんが書きました:配置newの場合はdeleteする必要は無いという理解でよいのでしょうか?
  • deleteしてはいけません
  • デストラクタは明示的に呼び出さなければなりません
MoNoQLoREATOR さんが書きました:例外処理についてなのですが。
>>ユーザーへの通知は最終的なプロダクトを作成する側の役割です。
>>ライブラリが勝手にユーザーへ通知するのは,たいていの場合使いにくくなるだけです。
では何も言わずに強制終了させた方が良いのでしょうか?
今回の場合,必要なのは呼び出し元にエラーを通知することです。
方法は例外だったり,戻り値だったり,グローバル変数又はそれに類するものだったりしますが。
# 今回は例外がおそらく妥当。
強制終了などの重要な挙動は,本当にそれが妥当で,他に代替手段がないか考えた方が良いです。

Re: C2662エラーについて

Posted: 2012年3月08日(木) 09:38
by MoNoQLoREATOR
>>bitter_foxさん
>>YuOさん
返信ありがとうございます。

throw文を書けば、その後の処理はかってにやってくれるということでしょうか?
自分で定義しないといけないのかと思っていました。

例外が発生した場合の処理はユーザーに任せるという意味でしょうか。
本家vectorはどのような挙動でしたっけ?
範囲外アクセスが発生した旨が通知されてプログラムが終了したと思うのですが。

>>deleteしてはいけません
>>[* ]デストラクタは明示的に呼び出さなければなりません
fw::vector<X>::~X の事でしょうか。見落としていたデストラクタの修正を行いました。
↓デストラクタの部分だけ抜き出し
► スポイラーを表示

Re: C2662エラーについて

Posted: 2012年3月08日(木) 11:41
by YuO
MoNoQLoREATOR さんが書きました:例外が発生した場合の処理はユーザーに任せるという意味でしょうか。
恐らく違うように考えていると思います。
ライブラリを使っている人,つまりはライブラリのユーザーに処理を任せます。
その結果,プロダクトのユーザーにMessageBoxで通知されるかもしれませんし,プログラムの内部で吸収されて処理されるかもしれません。

現在は「ライブラリの作成者」==「ライブラリのユーザー」なのだとは思いますが,この2つの立場は分けて考えた方がよいです。

MoNoQLoREATOR さんが書きました:本家vectorはどのような挙動でしたっけ?
範囲外アクセスが発生した旨が通知されてプログラムが終了したと思うのですが。
プログラムが終了する,というのはstd::vector::atの挙動の範囲ではありません。

atで範囲外アクセスが発生した場合,std::out_of_range_exceptionが発生します。
ライブラリのユーザーは,try - catchによって範囲外アクセスの発生を知ることが出来ます。
try - catchしないのはライブラリのユーザーの判断です。

MoNoQLoREATOR さんが書きました:>>deleteしてはいけません
>>[* ]デストラクタは明示的に呼び出さなければなりません
fw::vector<X>::~X の事でしょうか。
placement-newに関する一般論です。

Re: C2662エラーについて

Posted: 2012年3月08日(木) 14:50
by MoNoQLoREATOR
>>YuOさん
返信ありがとうございます。

通常は例外を投げるだけで、#defineにて特定の定数が定義されている場合のみメッセージボックスが表示される仕様にしてみました。

該当部分↓
► スポイラーを表示

Re: C2662エラーについて

Posted: 2012年3月08日(木) 15:02
by softya(ソフト屋)
C++ではアンダースコアで始まる識別子はシステムの予約語です。
あとダブル・アンダースコアは識別子の何処に書いても予約語です。

「命名規則 (プログラミング) - Wikipedia」
http://ja.wikipedia.org/wiki/%E5%91%BD% ... A8_C.2B.2B
「take short 二重インクルード防止のシンボル」
http://pdragon.blog24.fc2.com/blog-entry-146.html

Re: C2662エラーについて

Posted: 2012年3月08日(木) 18:10
by MoNoQLoREATOR
>>softya(ソフト屋)さん
返信ありがとうございます。修正しました。

自分のソースコードを見直してみて、問題はもう無いかなと思いましたので『解決』といたします。
softya(ソフト屋)さん,YuOさん,bitter_foxさん 本当にありがとうございました。