std :: mapのキーからリソースを盗むことはできますか?


15

C ++では、後で不要になったマップからリソースを盗んでも大丈夫ですか?より正確には、std::mapwith std::stringキーがあり、をmap使用してsキーのリソースを盗むことでそれからベクターを構築したいと仮定しますstd::move。キーへのそのような書き込みアクセスは、内部データ構造(キーの順序)を破壊することに注意してください。ただし、map後で使用することはしません。

質問:問題なくこれを実行できますか?または、意図されていないmap方法でアクセスしたために、たとえばのデストラクタで予期しないバグが発生std::mapしますか?

以下はプログラム例です。

#include<map>
#include<string>
#include<vector>
#include<iostream>
using namespace std;
int main(int argc, char *argv[])
{
    std::vector<std::pair<std::string,double>> v;
    { // new scope to make clear that m is not needed 
      // after the resources were stolen
        std::map<std::string,double> m;
        m["aLongString"]=1.0;
        m["anotherLongString"]=2.0;
        //
        // now steal resources
        for (auto &p : m) {
            // according to my IDE, p has type 
            // std::pair<const class std::__cxx11::basic_string<char>, double>&
            cout<<"key before stealing: "<<p.first<<endl;
            v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
            cout<<"key after stealing: "<<p.first<<endl;
        }
    }
    // now use v
    return 0;
}

それは出力を生成します:

key before stealing: aLongString
key after stealing: 
key before stealing: anotherLongString
key after stealing: 

編集:私はこれを大きなマップのコンテンツ全体に対して行い、このリソースを盗むことによって動的な割り当てを保存したいと思います。


3
この「盗む」の目的は何ですか?マップから要素を削除するには?それなら、なぜそれをしないのですか(マップから要素を削除します)?また、const値の変更は常に UBです。
プログラマー

どうやらそれは深刻なバグにつながるでしょう!
rezaebrh

1
あなたの質問への直接の答えではありませんが:ベクトルではなく範囲またはイテレータのペアを返す場合はどうでしょうか?それは完全にコピーすることを避けます。いずれの場合も、最適化の進行状況を追跡するためのベンチマークと、ホットスポットを見つけるためのプロファイラーが必要です。
Ulrich Eckhardt

1
@ ALX23zこのステートメントのソースはありますか?ポインタのコピーがメモリの全領域をコピーするよりもコストがかかる方法は想像できません。
セバスチャンホフマン

1
@SebastianHoffmannそれは最近のCppConで言及されましたが、どちらの話かはわかりません。事はstd::string短い文字列の最適化です。ポインタの交換だけでなく、コピーと移動にはいくつかの重要なロジックがあることを意味します。さらに、ほとんどの場合、移動はコピーを意味します。かなり長い文字列を処理しないようにしてください。統計の差はとにかく小さく、一般的にはどのような文字列処理が行われるかによって確かに異なります。
ALX23z

回答:


18

変数const_castを変更するために使用して、未定義の動作を行っていconstます。それをしないでください。これconstは、マップがキーでソートされているためです。したがって、キーをインプレースで変更すると、マップが構築されているという前提条件が崩れます。

を使用const_castconstて変数から削除したり、その変数変更したりしないでください。

そうは言っても、C ++ 17には問題の解決策があります:std::mapextract機能:

#include <map>
#include <string>
#include <vector>
#include <utility>

int main() {
  std::vector<std::pair<std::string, double>> v;
  std::map<std::string, double> m{{"aLongString", 1.0},
                                  {"anotherLongString", 2.0}};

  auto extracted_value = m.extract("aLongString");
  v.emplace_back(std::make_pair(std::move(extracted_value.key()),
                                std::move(extracted_value.mapped())));

  extracted_value = m.extract("anotherLongString");
  v.emplace_back(std::make_pair(std::move(extracted_value.key()),
                                std::move(extracted_value.mapped())));
}

そして、しないでくださいusing namespace std;。:)


よろしくお願いします!しかし、私がしたように私ができないことを確信していますか?私は意味map、私は(私はしない)、そのメソッドを呼び出すことはありません、多分内部順序はそのデストラクタで重要でない場合は文句を言わないのだろうか?
phinz

2
マップのキーが作成されconstます。変異const物は何が実際に後でそれらにアクセスするかどうかにかかわらず、インスタントUBです。
HTNW

この方法には2つの問題があります。(1)すべての要素を抽出したいので、キー(非効率なルックアップ)ではなくイテレータで抽出したい。これも可能であることがわかったので、これで問題ありません。(2)私が間違っている場合は私を修正しますが、すべての要素を抽出すると、膨大なオーバーヘッドが発生します(抽出ごとに内部ツリー構造を再調整するため)?
phinz

2
@phinz イテレータを引数として使用すると、cppreferenceで わかるようにextract一定の複雑さを償却します。ある程度のオーバーヘッドは避けられませんが、おそらく問題になるほど重要ではありません。これでカバーされていない特別な要件がある場合は、mapこれらの要件を満たす独自の要件を実装する必要があります。std容器は、共通の汎用application.Theyのために意図されている特定のユースケースに最適化されていません。
ウォールナット

@HTNWキーが作成されましたconstか?この場合、私の主張がどこで間違っているかを教えてください。
phinz

4

druckermanlyの答えが正しく指摘しconstているように、コードはオブジェクトを変更しようとするため、未定義の動作をします。

他のいくつかの回答(phinzDeuchieの回答)constは、マップからノードを抽出した結果のノードハンドルがconstキーへの非アクセスを許可しているため、キーをオブジェクトとして保存してはならないことを主張しています。この推論は最初はもっともらしいと思われるかもしれませんが、機能性を紹介したペーパーであるP0083R3には、extractこのトピックに関する専用のセクションがあり、この議論を無効にします。

懸念

この設計に関していくつかの懸念が提起されています。ここでそれらに対処します。

未定義の動作

理論的な観点からこの提案の最も難しい部分は、抽出された要素がそのconstキータイプを保持しているという事実です。これにより、移動したり変更したりできなくなります。これを解決するために、ノードハンドルが保持する要素のキーへの非constアクセスを提供するキーアクセサー関数を提供しました。この関数は、コンパイラの最適化の存在下で正しく機能することを保証するための実装「マジック」を必要とします。これを行う1つの方法は、pair<const key_type, mapped_type> およびの結合ですpair<key_type, mapped_type>。これらの間の変換はstd::launder、抽出と再挿入で使用されるのと同様の手法を使用して安全に行うことができます 。

これが技術的または哲学的な問題を引き起こすとは考えていません。標準ライブラリが存在する理由の一つは、クライアントは、ポータブルC ++(例えばで書くことができない非ポータブルと魔法のコード記述することです<atomic><typeinfo><type_traits>、など)。これは、もう1つの例です。コンパイラベンダーがこの魔法を実装するために必要なのは、最適化の目的でユニオンの未定義の動作を利用しないことだけです。現在、コンパイラはこれを(ここで利用されている範囲で)すでに約束しています。

これは、これらの関数が使用される場合、std::pairpair<const key_type, mapped_type>は異なるレイアウトを持つ ように特化することができないという制限をクライアントに課しますpair<key_type, mapped_type>。実際にこれを実行したいという可能性は事実上ゼロであると私たちは感じており、正式な表現ではこれらのペアの特殊化を制限しています。

キーメンバー関数は、このようなトリックが必要な唯一の場所であり、コンテナーまたはペアの変更は必要ないことに注意してください。

(強調鉱山)


これは実際には元の質問に対する回答の一部ですが、私は1つしか受け入れることができません。
phinz

0

const_castこの場合、変更によって未定義の動作が発生するとは思わないが、この引数が正しいかどうかコメントしてください。

この回答は、

つまり、元々constオブジェクトを変更した場合はUBを取得し、それ以外の場合はUBを取得します。

したがって、v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));質問の行は、stringオブジェクトp.firstがconstオブジェクトとして作成されていない場合にのみ、UBにつながりません。今すぐご注意 についての参照extract状態

ノードを抽出すると、抽出された要素に対する反復子が無効になります。抽出された要素へのポインタと参照は有効なままですが、要素がノードハンドルによって所有されている間は使用できません。要素がコンテナに挿入されると、それらは使用可能になります。

私があれば対応する、その保管場所でのライブを続けています。しかし、抽出後、druckermanlyの回答のコードのように、リソースを削除することが許可されます。この手段、したがってまた、オブジェクトがしていない元々 constオブジェクトとして作成されました。extractnode_handleppmoveppstringp.first

したがって、mapのキーの変更はUBにつながらず、Deuchieの答えから、の破損したツリー構造(現在は複数の同じ空の文字列キー)mapもデストラクタに問題を引き起こさないと思われます。したがって、問題のコードは、少なくともextractメソッドが存在するC ++ 17 (およびポインタが有効なホールドのままであるというステートメント)では正常に機能するはずです。

更新

この答えは間違っていると私は考えています。他の回答で参照されているため削除していません。


1
申し訳ありません、フィンツ、あなたの答えは間違っています。私はこれを説明する答えを書きます-それは労働組合と関係がありstd::launderます。
LF

-1

編集:この答えは間違っています。種類のコメントは間違いを指摘しましたが、他の回答で参照されているため、削除はしません。

@druckermanlyが最初の質問に回答しました。この質問では、キーをmap強制的に変更すると、mapの内部データ構造が構築されている秩序が崩れます(赤黒ツリー)。ただし、このextractメソッドは2つのことを行うため、安全に使用できます。キーをマップの外に移動してから削除するため、マップの秩序性にはまったく影響しません。

解体時に問題が発生するかどうかについて、あなたが提起したもう1つの質問は問題ではありません。マップが分解されるとき、マップはその各要素(mapped_typesなど)の分解子を呼び出し、moveメソッドはクラスが移動された後に安全に分解されることを保証します。心配しないでください。簡単に言えば、move「移動された」クラスに新しい値を削除または再割り当てしても安全であることを保証するのは、その操作です。具体的にstringは、moveメソッドはそのcharポインタをに設定する場合があるnullptrため、元のクラスのデコンストラクタが呼び出されたときに移動された実際のデータは削除されません。


コメントは私が見落としていた点を思い出させました、基本的に彼は正しかったですが、私が完全に同意しない1つの事柄があります:const_castおそらくUBではありません。constコンパイラと私たちの間の約束です。バイナリ形式での型と表現の点で、constまだオブジェクトであると記載されているオブジェクトconst。ときにconst捨てている、それは通常の可変クラスであるかのように、それが振る舞うべき。に関してはmove、それを使用したい場合は、の&代わりにを渡す必要がありますconst &。そのため、私はそれがUBではないので、単に約束を破っconstてデータを移動します。

また、MSVC 14.24.28314とClang 9.0.0をそれぞれ使用して2つの実験を行ったところ、同じ結果が得られました。

map<string, int> m;
m.insert({ "test", 2 });
m.insert({ "this should be behind the 'test' string.", 3 });
m.insert({ "and this should be in front of the 'test' string.", 1 });
string let_me_USE_IT = std::move(const_cast<string&>(m.find("test")->first));
cout << let_me_USE_IT << '\n';
for (auto const& i : m) {
    cout << i.first << ' ' << i.second << '\n';
}

出力:

test
and this should be in front of the 'test' string. 1
 2
this should be behind the 'test' string. 3

文字列「2」が空になっていることがわかりますが、空の文字列を前面に再配置する必要があるため、マップの秩序性が壊れています。マップの特定のノードを挿入、検索、または削除しようとすると、大惨事が発生する可能性があります。

いずれにせよ、パブリックインターフェイスをバイパスしてクラスの内部データを操作することは決して良いことではないことに同意するかもしれません。findinsertremoveなどの機能とは、内部データ構造の秩序に自分の正しさを依存している、そしてそれは我々が内部をのぞき見の思想から離れなければならない理由です。


2
「解体時に問題が発生するかどうかは問題ではない。」未定義の動作(const値の変更)が以前に発生したため、技術的に正しい。ただし、「move[関数]はクラスの[オブジェクトの]移動後に安全に分解できることを保証します」という引数は成立しません。constオブジェクト/参照から変更する必要があるため、安全に移動することはできません。const防ぐ。を使用してその制限を回避することもできconst_castますが、その時点では、UBでなくても、実装固有の動作に深く入り込んでいます。
hoffmale

@hoffmaleありがとう、私は見落とし、大きな間違いを犯しました。あなたがそれを指摘しなかった場合、ここでの私の答えは他の誰かを誤解させるかもしれません。実際、このmove関数はの&代わりconst&にを使用すると言う必要がありますconst_cast。そのため、キーをマップの外に移動すると主張した場合は、を使用する必要があります。
Deuchie

1
「constとして示されているオブジェクトは、バイナリ形式での型と表現の点で、constを持たないオブジェクトと同じオブジェクトです」いいえ、constオブジェクトは読み取り専用メモリに配置できます。また、constを使用すると、コンパイラーは複数の読み取り用のコードを生成する代わりに、コードを推論して値をキャッシュできます(これにより、パフォーマンスの点で大きな違いが生じる可能性があります)。そのため、によって引き起こされるUB const_castは不快なものになります。ほとんどの場合機能しますが、コードを微妙に壊します。
LF

しかし、ここでは、オブジェクトが読み取り専用メモリに配置されていないことを確認できると思います。なぜなら、の後extractは、まったく同じオブジェクトからの移動が許可されるからですよね?(私の回答を参照)
phinz

1
申し訳ありませんが、回答の分析は間違っています。私はこれを説明する答えを書きます-それは組合と何か関係がありますstd::launder
LF
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.