web-dev-qa-db-ja.com

ベクトルと定数

このことを考慮

 void f(vector<const T*>& p)
 {
 }
 int main()
 { 
  vector<T*> nonConstVec;
  f(nonConstVec);
 }

以下はコンパイルされません。問題は、vector<T*>vector <const T*>に変換できないことです。これは、T*からconst T*への暗黙の変換が存在するため、私には非論理的に思えます。 。どうしてこれなの ?

vector<const T*>vector <T*>に変換できませんが、const T*を暗黙的にT*に変換できないため、これは予想されます。

23
user152508

コードに数行追加しました。これが許可されない理由を明確にするには、これで十分です。

void f(vector<const T*>& p)
 {
    static const T ct;
    p.Push_back(&ct); // adds a const T* to nonConstVec !
 }
 int main()
 { 
  vector<T*> nonConstVec;
  f(nonConstVec);
  nonConstVec.back()->nonConstFunction();
 }
38
MSalters

vector<T>vector<const T>は無関係なタイプです。 Tconst Tに変換できるという事実は、ここでは意味がありません。

型システムの観点から考える必要があります。インスタンス化されたvector<int>には、vector<const int>との共通点はありません。

22

必要な変換を実行することがconst-correctnessの違反である理由を示す価値があるかもしれません:

#include <vector>
const int a = 1;

void addConst(std::vector<const int *> &v) {
    v.Push_back(&a); // this is OK, adding a const int* to a vector of same
}

int main() {
    std::vector<int *> w;
    int b = 2;
    w.Push_back(&b);  // this is OK, adding an int* to a vector of same
    *(w.back()) = 3;  // this is OK, assigning through an int*
    addConst(w);      // you want this to be OK, but it isn't...
    *(w.back()) = 3;  // ...because it would make this const-unsafe.
}

問題は、vector<int*>.Push_backが非constへのポインターを受け取ることです(これを今後「非constポインター」と呼びます)。つまり、パラメータのポインティを変更する可能性があります。特にベクトルの場合、ポインタを変更した他の誰かにポインタを戻す可能性があります。したがって、wのPush_back関数にconstポインターを渡すことはできません。また、テンプレートシステムがサポートしている場合でも、必要な変換は安全ではありません(サポートしていません)。 const-safetyの目的は、constポインターを、非constポインターを受け取る関数に渡さないようにすることです。これが、その仕事のやり方です。 C++では、安全でないことをしたい場合は具体的に言う必要があるため、変換を暗黙的に行うことはできません。実際、テンプレートがどのように機能するかにより、それはまったく不可能です(後述)。

vector<T*>&からconst vector<const T*>&への変換が安全であるのと同様に、C++は原則としてint **からconst int *const *への変換を許可することでconst-safetyを維持できると思います。しかし、それはベクトルの定義方法によるものです。他のテンプレートに対して必ずしもconst-safeであるとは限りません。

同様に、理論的には明示的な変換が可能です。そして実際、それは明示的な変換を可能にしますが、参照ではなくオブジェクトに対してのみです;-)

std::vector<const int*> x(w.begin(), w.end()); // conversion

参照用にそれを行うことができない理由は、テンプレートシステムがそれをサポートできないためです。変換が許可された場合に壊れる別の例:

template<typename T> 
struct Foo {
    void Bar(T &);
};

template<>
struct Foo<const int *> {
    void Baz(int *);
};

現在、Foo<int*>にはBaz関数がありません。一体どうやってFoo<int*>へのポインタまたは参照をFoo<const int*>へのポインタまたは参照に変換できるでしょうか?

Foo<int *> f;
Foo<const int *> &g = f; // Not allowed, but suppose it was
int a;
g.Baz(&a); // Um. What happens? Calls Baz on the object f?
13
Steve Jessop

このように考えてください:

次のような2つのクラスがあります。

class V  { T*       t;};
class VC { T const* t;};

これらの2つのクラスが自動的に変換可能になると思いますか?
これは基本的にテンプレートクラスです。それぞれのバリエーションは全く新しいタイプです。

したがって、vector <T *>とvector <T const *>は完全に異なるタイプです。

私の最初の質問は、本当にポインタを保存したいですか?

はいの場合は、boost :: ptr_containerを確認することをお勧めします。これはポインタを保持し、ベクトルが破壊されたときにそれらを削除します。しかし、もっと重要なことは、含まれているポインタを通常のstd:vectorとして扱うことです。したがって、ベクトルをconstにすると、そのメンバーにconstとしてのみアクセスできます。

void function(boost::ptr_vector<T> const& x)
{
     x.Push_back(new T);  // Fail x is const.
     x[4].plop();         // Will only work if plop() is a const member method.
}

ポインタを格納する必要がない場合は、オブジェクト(ポインタではなく)をコンテナに格納します。

void function(std::vector<T> const& x)
{
     x.Push_back(T());    // Fail x is const.
     x[4].plop();         // Will only work if plop() is a const member method.
}
6
Martin York

あなたが与えたコードがコンパイルされない理由をすでに示している人もいますが、私はそれをどのように扱うかについて別の答えを持っています。 2つを自動的に変換する方法をコンパイラーに教える方法はないと思います(std::vectorの定義を変更する必要があるため)。この煩わしさを回避する唯一の方法は、明示的な変換を行うことです。

完全に異なるベクトルに変換することは満足のいくものではありません(完全に同一であるはずの何かのためにメモリとサイクルを浪費します)。私は次のことを提案します:

#include <vector>
#include <iostream>

using namespace std;

typedef int T;

T a = 1;
T b = 2;

void f(vector<const T*>& p)
{
    for (vector<const T*>::const_iterator iter = p.begin(); iter != p.end(); ++iter) {
        cout << **iter << endl;
    }
}
vector<const T*>& constify(vector<T*>& v)
{
  // Compiler doesn't know how to automatically convert
  // std::vector<T*> to std::vector<T const*> because the way
  // the template system works means that in theory the two may
  // be specialised differently.  This is an explicit conversion.
  return reinterpret_cast<vector<const T*>&>(v);
}
int main()
{
  vector<T*> nonConstVec;
  nonConstVec.Push_back(&a);
  nonConstVec.Push_back(&b);
  f(constify(nonConstVec));
}

私はreinterpret_castを使用して、2つのことが同じであることを宣言しています。あなた[〜#〜] should [〜#〜]使用後に汚れた感じがしますが、それ自体を関数に入れて、あなたをフォローしている人へのコメントを付けたら、洗って良心を持って道を進んでください。ただし、誰かがあなたの下から地面を引き抜くのを常に(当然のことながら)心配します。

4
Paul Price

他の人が言っているように、変換はテンプレートパラメータに適用されません。言い換えれば、

vector<T>

...そして:

vector<const T>

...完全に異なるタイプです。

ベクトルの内容を変更しないf()に関してconst-correctnessを実装しようとしている場合、これはあなたが探しているものに沿っている可能性があります。

void f(vector<T>::const_iterator begin, vector<T>::const_iterator end)
{
  for( ; begin != end; ++begin )
  {
    // do something with *begin
  }
}

int main()
{
  vector<T> nonConstVec;
  f(nonConstVec.begin(), nonConstVec.end());
}
3
John Dibling

他の回答に加えて、これ(および他の多くのC++機能)が重要なPOVから説明されているC++ FQA Liteを読む価値があります: http://yosefk.com/c++fqa/const.html#fqa -18.1

1
marcin

これがテンプレートの動作方法です。テンプレートパラメータに変換が適用されないため、2つのベクトルは完全に異なるタイプになります。

0
anon

テンプレートはそのように少し奇妙です。 TからUへの暗黙の変換があるという事実は、XXXからXXXへの暗黙の変換があることを意味しません。それを実現することはできますが、それを実現するにはテンプレートコードでかなりの余分な作業が必要であり、std::vectorが設計されたときにテクニックがすべて知られているとは思えません(より正確には、私はm彼らが知られていないことはかなり確かです)。

編集:このような問題は、イテレータを使用する背後にある動機の一部です。 container of Xは暗黙的にcontainer of const Xに変換可能ではありませんが、container<X>::iteratoris暗黙的にcontainer<X>::const_iteratorに変換可能です。

交換する場合:

void f(vector<const T*>& p) {}

と:

template <class const_iter>
void f(const_iter b, const_iter e) {}

次に:

int main() { 
    vector<T*> nonConstVec;
    f(nonConstVec.begin(), nonConstVec.end());
    return 0;
}

うまくいくでしょう-そしてそうなるでしょう:

vector<T const *> constVec;
f(constVec.begin(), constVec.end());
0
Jerry Coffin

_vector<const T*>_と_vector<T*>_はどちらも完全に異なるタイプです。 main()内に_const T*_を記述しても、コードはコンパイルされません。 main内に特殊化を提供する必要があります。

以下をコンパイルします。

_ #include<vector>
 using namespace std;

 template<typename T>
 void f(vector<const T*>& p)
 {
 }
 int main()
 { 
     vector<const int*> nonConstVec;
     f(nonConstVec);
 }
_
0
Prasoon Saurav