std::stringで CString::GetBufferみたいなメソッドを使いたい…

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
けえぼお

std::stringで CString::GetBufferみたいなメソッドを使いたい…

#1

投稿記事 by けえぼお » 15年前

初めて質問させていただきます。どうぞよろしくおねがいします。

自分は最近STLを学び始めたばかりの初心者ですので、ごく簡単な質問かもしれませんが回答いただけると嬉しいです。

よくMFCのCString::GetBufferを使って下記のようにWindowsAPIの関数から文字列を取得するコードがありますが、
調べてもそれに当たるstd::stringのメソッドは見つかりませんでした。
どうして提供されていないのかすごく気になります。
CString cStr;
    int iLength = ::GetWindowTextLength( GetSafeHwnd() );
    ::GetWindowText( GetSafeHwnd(), cStr.GetBuffer( iLength ), iLength + 1 );
    cStr.ReleaseBuffer();
Googleで調べてみたところ英語のサイト(http://bytes.com/topic/c/answers/805854 ... -getbuffer)が出てきましたが、英語力の乏しい私にはどうも無理らしいということを理解するので精いっぱいでした。
どなたか教えていただけないでしょうか?

たかぎ

Re:std::stringで CString::GetBufferみたいなメソッドを使いたい…

#2

投稿記事 by たかぎ » 15年前

> どうして提供されていないのかすごく気になります。

CString::GetBufferのようなメンバ関数はカプセル化を破壊してしまうからです。

けえぼお

Re:std::stringで CString::GetBufferみたいなメソッドを使いたい…

#3

投稿記事 by けえぼお » 15年前

ためしに確かめて(?)みたところReleaseBufferする前は当然として、した後もGetBufferで取得したのを書き換えることができました。
とてもカプセル化できているとはいえないと思うのですが、
これはそもそもGetBufferのようなメソッドがあるのが間違いだということですか?

CStringW cWStr;
wchar_t *pwch = cWStr.GetBuffer( 30 );
wcscpy( pwch, L"あいうえお" );
MessageBox( cWStr );// あいうえお
cWStr.ReleaseBuffer();
wcscpy( pwch, L"かきくけこ" );
MessageBox( cWStr );// かきくけこ

たかぎ

Re:std::stringで CString::GetBufferみたいなメソッドを使いたい…

#4

投稿記事 by たかぎ » 15年前

> これはそもそもGetBufferのようなメソッドがあるのが間違いだということですか?

正しくカプセル化するという観点からはそうです。
もっとも、MFCはそもそもそんなことを目指したライブラリではありませんので、これはこれでよいのかもしれません。
ただし、同じ設計思想をstd::basic_stringに期待するのは無理があります。

けえぼお

Re:std::stringで CString::GetBufferみたいなメソッドを使いたい…

#5

投稿記事 by けえぼお » 15年前

丁寧に答えていただいてありがとうございます。
それと、なんだか質問ばかりで申し訳ないんですが、WindowsAPIから文字列を取得するときどうやるのが普通なのでしょうか?
あるいはたかぎさんはいつもどうやっていますか?

文字数が決まっている場合ならTCHAR ptch[xxxx];で十分でしょうが、
文字数が可変のときはいちいち
TCHAR ptch = new TCHAR[xxxx];
...
delete[/url] ptch;
などと書くのは面倒な上、delete[/url]を呼び出すのを忘れてしまいそうです。

たかぎ

Re:std::stringで CString::GetBufferみたいなメソッドを使いたい…

#6

投稿記事 by たかぎ » 15年前

MFCを使うのであれば、多重定義されたCWnd::GetWindowTextで直接CStringに読み込むのでしょうが、そうでない場合、

> 文字数が可変のときはいちいち
> TCHAR ptch = new TCHAR[xxxx];
> ...
> delete[/url] ptch;
> などと書くのは面倒な上、delete[/url]を呼び出すのを忘れてしまいそうです。

基本はこの方法だと思います。
一度、std::basic_string<TCHAR>用に多重定義したGetWindowText関数を作れば、面倒でもないでしょう。
文字列がそんなに長くないことがわかっているなら、_alloca等を使うという手もありますね。

まあ、次のような手もないわけではありませんが...

int length = GetWindowTextLength(hWnd);
std::basic_string<TCHAR> str(length + 1, _T('\0'));
GetWindowText(hWnd, &str[0], length + 1);
str.resize(length);

YuO

Re:std::stringで CString::GetBufferみたいなメソッドを使いたい…

#7

投稿記事 by YuO » 15年前

std::vector<TCHAR>使うのが定石ですね。
一端resizeしてしまえば,ポインタは有効なままですし,
サイズがnのstd::vector<TCHAR> vについて,
&v[0] + i == &v (0 <= i && i < n)が標準で定まっていますから (IS 14882:2003での追加)。
あとは,std::basic_string<TCHAR>::assignなどでbasic_stringに変換します。

めるぽん

Re:std::stringで CString::GetBufferみたいなメソッドを使いたい…

#8

投稿記事 by めるぽん » 15年前

> 文字数が可変のときはいちいち
> TCHAR ptch = new TCHAR[xxxx];
> ...
> delete[/url] ptch;
> などと書くのは面倒な上、delete[/url]を呼び出すのを忘れてしまいそうです。
std::basic_string に格納しなくても構わないというのであれば、スコープアウトするときに delete[/url] してくれるクラスを作るか、もしくはそういったライブラリ(Boost の scoped_array など)を探してきて使うのがいいでしょうね。

そういうとき、自分ならこういうクラスを作ります(自分で作るとバグが増えるのであまりおすすめしませんが)。
struct tchar_scoped_string
{
    explicit tchar_scoped_string(std::size_t len)
        : p(new TCHAR[len + 1]), len(len)
    {
        p[len] = TCHAR();
    }
    ~tchar_scoped_string() { delete[/url] p; }
    TCHAR* get() const { return p; }
    TCHAR& operator[/url](std::size_t n) { assert(n < length()); return p[n]; }
    const TCHAR& operator[/url](std::size_t n) const { assert(n < length()); return p[n]; }
    std::size_t length() const { return len; }
    std::size_t bufsize() const { return len + 1; }

private:
    // コピー禁止
    tchar_scoped_string(const tchar_scoped_string&);
    void operator=(const tchar_scoped_string&);

private:
    TCHAR* const p;
    const std::size_t len;
};
{
    tchar_scoped_string str(GetWindowTextLength(hWnd));
    GetWindowText(hWnd, str.get(), str.bufsize());

    // str を使って適当に何かする
} // ここで str の内部で確保しているメモリが delete[/url] される

ただどうしても GetBuffer と ReleaseBuffer にしたい!というのであれば、たかぎさんが書かれているコードを分けてしまって以下のような関数にしてしまってもいいのかもしれません(微妙に GetBuffer や ReleaseBufer とは仕様が異なります)。
もちろん推奨はしませんが……。
TCHAR* GetBuffer(std::basic_string<TCHAR>& str, std::size_t n)
{
    str.resize(n + 1);
    return &str[0];
}
void ReleaseBuffer(std::basic_string<TCHAR>& str)
{
    str.resize(str.size() - 1);
}
std::basic_string<TCHAR> str;
int length = ::GetWindowTextLength( GetSafeHwnd() );
::GetWindowText( GetSafeHwnd(), GetBuffer( str, length ), length + 1 );
ReleaseBuffer(str);
あと _alloca を使う方法もありますが、ATL を使っていいような環境なら、_alloca よりは _ATL_SAFE_ALLOCA を使った方がいいですね。

けえぼお

Re:std::stringで CString::GetBufferみたいなメソッドを使いたい…

#9

投稿記事 by けえぼお » 15年前

3人も回答していただけるとは…
いろいろと勉強不足で、書かれている内容を調べながら理解していたらずいぶん時間がかかってしまいました。

>>std::basic_string<TCHAR> 用に多重定義したGetWindowText関数を作れば、面倒でもないでしょう。
初心者なりに頑張って考えてみました。
厚かましいようですが、直すべき点を指摘していただけないでしょうか。
int GetWindowText( HWND p_hWnd, std::basic_string<TCHAR> *p_pcStr )
{
    int iLength = GetWindowTextLength( p_hWnd );
    if( iLength <= 0 )
        return 0;
    TCHAR *ptch = new TCHAR[iLength + 1];
    if( GetWindowText( p_hWnd, ptch, iLength + 1 ) <= 0 )
        return 0;
    p_pcStr->assign( ptch, ptch + iLength );
    delete[/url] ptch;
    return iLength;
}
>>文字列がそんなに長くないことがわかっているなら、_alloca等を使うという手もありますね。
スタックに割り当てる関数なんてあったんですね。すごく便利です!

>>std::vector<TCHAR>使うのが定石ですね。
&v[0]をポインタとして使っても良いだなんて驚きです。勉強になります!
std::vector<TCHAR> cVector;
std::basic_string<TCHAR> cStr;
int iLength = GetWindowTextLength( hWnd );
if( iLength <= 0 )
    return 0;
cVector.resize( GetWindowTextLength( hWnd ) + 1 );
if( GetWindowText( hWnd, &cVector[0], GetWindowTextLength( hWnd ) + 1 ) <= 0 )
    return 0;
cStr.assign( &cVector[0], &cVector[0] + GetWindowTextLength( hWnd ) );
↑Yuoさんはこんな感じでやるんでしょうか?

>>std::basic_string に格納しなくても構わないというのであれば、スコープアウトするときに delete[/url] してくれるクラスを作るか、もしくはそういったライブラリ (Boost の scoped_array など)を探してきて使うのがいいでしょうね。
調べたらstd::auto_ptrが出てきました。でもdelete[/url]には対応してないんですね。やはり自作でしょうか。

>>そういうとき、自分ならこういうクラスを作ります
わざわざこんな長いコードを書いてくださってありがとうございます。
explicit、const、assert等色々勉強させて頂きました。

>>あと _alloca を使う方法もありますが、ATL を使っていいような環境なら、_alloca よりは _ATL_SAFE_ALLOCA を使った方がいいですね。
_allocaには安全性面で弱点があったんですね。。。
便利なので使い倒そうと思っていたんですが、、、確保する領域が小さい時だけに限るようにします。

_allocaについて調べて疑問に思ったのですが、
_allocaで動的にスタックから領域を確保できるなら、どうしてCの配列は静的配列にする必要があったんでしょうか?
動的配列のほうが断然便利だと思うのですが。


ところで、皆さんが苦労して得た知識を素人が簡単に教えてもらうのはどうかと思えてきました。
このまま質問していていいんでしょうか?

めるぽん

Re:std::stringで CString::GetBufferみたいなメソッドを使いたい…

#10

投稿記事 by めるぽん » 15年前

いろいろ調べて頂けたみたいでなによりです。
// この関数の仕様が無いので、どういう動作をするのかが正しいのか分かりません。
// なので、基本は GetWindowText と同じ動作をしたい、という前提で書きます
int GetWindowText( HWND p_hWnd, std::basic_string<TCHAR> *p_pcStr )
{
    // p_pcStr が NULL の可能性があります
    // std::basic_string<TCHAR>& にするか、p_pcStr の NULL チェックを入れた方がいいかも

    // GetWindowTextLength は、失敗したときと、
    // キャプションが空の文字列になっているときも 0 を返すそうです。
    // 空の文字列は別に失敗でも何でもないので、
    // 0 が返されたときに p_pcStr が更新されていないのはまずいかも?
    int iLength = GetWindowTextLength( p_hWnd );
    if( iLength <= 0 )
        return 0;
    // ここで例外が投げられる可能性があります。
    // なので GetWindowText と動作が大きく異なってしまう可能性があります
    TCHAR *ptch = new TCHAR[iLength + 1];
    if( GetWindowText( p_hWnd, ptch, iLength + 1 ) <= 0 )
        return 0; // ここで return すると ptch がリークします

    // p_pcStrはここでしか変更していないのですが、
    // ドキュメントによると GetWindowText で失敗した場合、
    // ptch には空の文字列が渡されるそうです。
    // GetWindowText という名前にするのであれば、動作をあわせるため、失敗したときに
    // p_pcStr->clear() を呼び出した方がいいかもしれません。
    p_pcStr->assign( ptch, ptch + iLength );
    // あと、これは自分が書いたコードにも問題があったのですが、
    // ptch の有効な文字列は ptch + iLength まであるとは限らないので、
    // GetWindowText の戻り値を見て、ptch + result とする必要がありそうです。

    delete[/url] ptch;
    return iLength;
}
// 上記の指摘は結構厳しめに見ています。
// 実際 Windows でメモリが足りないなんてことはまず無いでしょうし、
// NULL の std::basic_string を渡す人もまずいないでしょう。
ということで、自分が作るならこんな感じになりますね
// 基本的には ::GetWindowText と同じ動作をします。
// 違いは、自動的に長さを計算して std::basic_string に設定することと、
// メモリが足りない場合に例外が投げられる可能性があることです。
// 例外が投げられた場合、str.size() は不定の大きさになります。
int GetWindowText(HWND hwnd, std::basic_string<TCHAR>& str)
{
    str.clear();
    int len = GetWindowTextLength(hwnd);
    //try
    //{
        str.resize(len + 1); // new は怖いので...
        int result = GetWindowText(hwnd, &str[0], str.size());
        str.resize(result);
    //}
    //catch (std::exception&) // std::bad_alloc と std::length_error を catch
    //{
    //    return 0;
    //}
    return result;
}
例外についても指摘したので、最初は例外を catch していたのですが、
GetWindowText で 0 で返された場合と区別が付かなくなるので、
そのまま上に伝えた方がいいかなーと思ってコメントアウトしました。

# 何度か読み直しましたが、自分のコードもまだ何か抜けがありそうで怖いところ……。

>ところで、皆さんが苦労して得た知識を素人が簡単に教えてもらうのはどうかと思えてきました。
>このまま質問していていいんでしょうか?
ここは質問する場所ですし、答えたくない人は書き込まなかったらいいだけじゃないかなーと。

けえぼお

Re:std::stringで CString::GetBufferみたいなメソッドを使いたい…

#11

投稿記事 by けえぼお » 15年前

おお…的確な指摘をどうもありがとうございます!
大変参考になりました!
あと、返事が遅くなって申し訳ありませんでした。

// std::basic_string<TCHAR>& にするか、p_pcStr の NULL チェックを入れた方がいいかも
NULLが渡されることは全然考えてませんでした。
MFCでCStringをバッファとして要求するメソッドはCString &をとることが多い気がするので、真似して参照を使おうと思います。
参照を使うとそんなメリットがあったなんて。

// ここで return すると ptch がリークします
あああ。。。
なんでこんなミスをしたんでしょう。
関数を抜けるときはdeleteを忘れないようにしないといけないですね。

// ドキュメントによると GetWindowText で失敗した場合、
// ptch には空の文字列が渡されるそうです。
// GetWindowText という名前にするのであれば、動作をあわせるため、失敗したときに
// p_pcStr->clear() を呼び出した方がいいかもしれません。
自分がドキュメントを読めてなかったことがよくわかります。
GetWindowTestという名前にするのなら正確に仕様を理解しておくべきでした。

// 実際 Windows でメモリが足りないなんてことはまず無いでしょうし、
メモリが足りなかったときは、アプリケーションはそのまま終了すべきだ。
と何処かに書いてあった気がするのでnewで投げられる例外はあまり気にしていないです。
一応、関数の仕様として例外が投げられることを明示しておくように心がけようと思います。



他にもいろいろ聞きたいことはあるのですが、スレッドのタイトルと合わなくなりそうなので、
この件については 解決! させておきました。
答えてくださった方々、ありがとうございました!

閉鎖

“C言語何でも質問掲示板” へ戻る