0

インターフェイスのユーザーから実装の詳細を隠し、テンプレート化された関数の広範な使用を避けるために、私は次の概念を考えました。

// data.h

#ifndef DATA_H_
#define DATA_H_

#include <cstddef>

template <size_t N = 0>
class Data
{
   public:
      const size_t n;
      size_t values[N];
      Data<N>();
};

#endif // DATA_H_

// data.cpp

#include "data.h"

template <size_t N> Data<N>::Data()
:
   n(N),
   values()
{
   for ( size_t i = 0; i < n; ++i )
   {
      values[i] = i;
   }
}

template class Data<1u>;
template class Data<2u>;

// list.h

#ifndef LIST_H_
#define LIST_H_

#include <cstddef>
#include <memory>

class List
{
   private:
      std::shared_ptr<void> data;
   public:
      List(const size_t);
      void printData() const;
};

#endif // LIST_H_

// list.cpp

#include "list.h"

#include <iostream>
#include <stdexcept>

#include "data.h"

List::List(const size_t n)
:
   data()
{
   switch ( n )
   {
      case 1u:
         data = std::static_pointer_cast<void>(std::make_shared<Data<1u>>());
         break;
      case 2u:
         data = std::static_pointer_cast<void>(std::make_shared<Data<2u>>());
         break;
      default:
         throw std::runtime_error("not instantiated..");
   }
}

void List::printData() const
{
   auto obj = std::static_pointer_cast<Data<>>(data);  // my question is about this
   std::cout << obj->n << ": ";
   for ( size_t i = 0; i < obj->n; ++i )
   {
      std::cout << obj->values[i] << " ";
   }
   std::cout << "\n";
}

// main.cpp

#include "list.h"

int main()
{
    for ( size_t i = 1; i <= 2; ++i )
    {
       try
       {
          List list(i);
          list.printData();
       }
       catch ( ... )
       {
          return 1;
       }
    }
}

これを恐ろしいデザインだと考える人もいるかもしれません。素晴らしい代替案がない限り、ここでこれについて議論しないでください。

私の質問はの行についてauto obj = std::static_pointer_cast<Data<>>(data);ですList::printData()。ちょっと危険な感じがします。正しいインスタンス化が使用されるという保証はありますか?g++-4.6.3このコードに対して警告を出さず、期待値を出力します。

4

2 に答える 2

2

はい。安全ではありません。あなたがキャストするときはいつでも、void*あなたはUBのリスクを冒します。コンパイラは、必要な型情報を持っていないため、警告を発していません。したがって、あなたがしていない正しいタイプにキャストするのはあなた次第です。

技術的に言えば、ここでは未定義の動作を引き起こしています。しかし、私の賭けは、それが通常は機能するということです。それはあなたがいつもCでしなければならないいくつかの厄介ながらくたと同じです。

それが機能する理由は、インスタンスのバイナリレイアウトが同じである可能性が高いためです。最初に「n」を付ける必要があります。これは、この厄介なトリックを実行している場合に必要です。その後に配列の先頭が続きます。

ポインタの領域外でこれを行う場合は、自分自身を台無しにすることになります。

オブジェクトが正しく削除される唯一の理由は、shared_ptrが作成時にデフォルトの削除機能を作成するため、正しいタイプを削除する方法を知っているためです。これを試してみると、他のスマートポインタのいずれかがあらゆる種類のBSを引き起こします。

編集:

さて、これを行うためのはるかに優れた方法は、型システムを使用して配列のサイズを決定することをやめることです。ランタイムに割り当てられた配列が本当に必要な場合は、ランタイムシステムを使用して配列を作成してください。とにかく無料ストアで作成しているので、このような型システムを悪用しても何のメリットもありません。リストコンストラクターに渡されたサイズに基づいて配列を割り当てるだけで、安全で予測可能な標準的な動作を実現できます。

于 2013-03-08T18:13:16.943 に答える
0

コンパイル時に解析されるstatic_castがあります。つまり、コンパイラに伝えているのは次のとおりです。

auto obj = std::static_pointer_cast<Data<>>(data); 

static_pointer_castdataタイプする変数std::shared_ptr<Data<>>
そして、デフォルトでは(テンプレートプロトタイプで宣言したように)Data<>はを意味しData<0>ます。

したがって、常に同じタイプのを取得しますshared_pointer

あなたができることは、インターフェースを作り、実行時にサイズを取得することです。

class IData 
{
  virutal size_t GetDataSize() = 0;
}

template <size_t N = 0>
class Data : public IData
{
   public:
      const size_t n;
      size_t values[N];
      Data<N>();
      virtual size_t GetDataSize() override { return N; }
};

次に、インターフェイスタイプのリストを保持し、data->GetDataSize();

さらに、テンプレートの実装を.cppファイルに入れないでください。使用されている場所を確認する必要があります。

于 2013-03-08T18:16:11.757 に答える