ArduinoでのVectorクラスの構築

私はこの問題を抱えているように思えます。  ベクタクラスを定義するとき、私は割り当てられたポインタを削除するときにいくつかの問題を抱えているようです。

奇妙なことに、これは私がメモリを "読んだ"後にのみ起こります。私が意味することは、一連の値をベクターにプッシュバックすると、そこにとどまるように見えるということです。しかし、一度値を読み込むと、データポインタが無効になってしまい、割り当てを解除しようとするとクラッシュします。

コード:

#ifndef VECTOR_H
#define VECTOR_H

#include      /* memcpy */

#define min(a,b) (a < b) ? a : b

template 
class vector {
public:
    vector() : __capacity(0), __count(0), __data(0) {} //empty constructor
    vector(T obj, int count = 1) {

        if (this->__data) {//free data ptr if used
            delete this->__data;
        }

        this->__data = new T[count];
        this->__count = count;
        this->__capacity = count;

        for (int i = 0; i < count; i++) //populate array with given object
            this->__data[i] = obj;

    }

    ~vector() {

        if (this->__data) {//free data ptr if used
            delete [] this->__data;
        }

        this->__count = this->__capacity = 0;
    }

    T const & operator[] (unsigned int idx) const {
        if (idx < this->__count) {
            return this->__data[idx];
        }
        else {
           //throw exception or handle error to be implemented
        }
    }

    T& operator[] (unsigned int idx) {
        if (idx < this->__count) {
            return this->__data[idx];
        }
        else {
           //throw exception or handle error to be implemented
        }
    }

    void resize_to_fit() {
        resize(this->__count);
    }

    T& pop_back(){
        return this->__data[--this->__count];
    }

    void push_back(T obj) {

        if (this->__capacity == this->__count) {
            resize(this->__capacity + 1);
        }

        this->__data[this->__count++] = obj;
    }

    bool isempty() {
        return !this->__data ||
            !this->capacity ||
            !this->size;
    }

    void clear() {
        this->~vector();
    }

    T* data() {
        return this->__data;
    }

    int size() {
        return this->__count;
    }

    int capacity() {
        return this->__capacity;
    }
private:
    void resize(int capacity) {
        if (this->__data == nullptr) {
            this->__data = new T[capacity];

            this->__count = 0;
            this->__capacity = capacity;
        }
        else if (capacity != this->__capacity) { //else do nothing

            T* data = new T[capacity];

            this->__count = min(this->__count, capacity);
            this->__capacity = capacity;

            memcpy(data, this->__data, sizeof(T) * this->__count);

            delete this->__data; //program crashes here, but the pointer is already broken...
            this->__data = data;
        }
    }
protected:

        int __capacity;
        int __count;
        T* __data;



};
#endif//VECTOR_H

私はArduinoでこのコードを使用していました。

void print_vec(vector v){
  Serial.println("Printing new vec!");
  for( int i = 0; i < v.size(); i++){
    Serial.println(v[i]);
  }
}

void setup() {
 //put your setup code here, to run once:

  Serial.begin(9600);//Open serial connection to report values to host

  while(!Serial); //Waiting for serial to open

  vector i = vector();

  i.push_back(10);
  i.push_back(2);
  print_vec(i); //prints 10 and 2, perfect so far
  i.push_back(3);

  while(true){
    print_vec(i); 
    i.pop_back();
    delay(2000);
  }
}

void loop() {
 //put your main code here, to run repeatedly:

}

このコードは次を出力します: 新しいvecを印刷する! 10 2 新しいvecを印刷する! 0 2 3 新しいvecを印刷する! 0 2 新しいvecを印刷する! 0 (...)

これを引き起こす原因は何ですか?私はしばらくの間ストーンウォールされていますが、これを解決する方法についての洞察は高く評価されます。ありがとう!

0
コード出力の最後に、出力の "(...)"部分が表示されているかどうかを確認します。そうでない場合、出力があなたが見たいものとどのように違うのですか?ベクトルに4つの項目がある場合、4つの i.pop_back()呼び出しでベクトルを空にする必要があります。
追加された 著者 Martin C. Martin,
これを削除する - > __ data; //ここでプログラムがクラッシュする... delete [] を正しく使用する必要がありますか?あなたのクラスにはプラットフォームコードがありませんので、より良いデバッグツールがあるPCでデバッグすることができます。
追加された 著者 Marius,

3 答え

2番目のコンストラクタでは、次のコードから始めます。

if (this->__data) {//free data ptr if used
    delete this->__data;
}//if

これは致命的です!あなたは __ data を初期化していないので、Sunの下で任意の値を保持できます。また、既存のデータへの有効なポインタである可能性がある方法はありません。これは、まったく新しい初期化されていないオブジェクトです。このゴミ・ポインタをヒープに戻すことは、単に問題を求めているだけです。

これらの行を削除してください。あるいは、最初のコンストラクタと同じように、初期化子リストを使用してください。

vector(T obj, int count = 1) :
       __data(new T[count]),
       __count(count),
       __capacity(count)
{
    for (int i = 0; i < count; i++) //populate array with given object
        __data[i] = obj;
}//vector(T, count)

別の問題:あなたが書いたあなたの clear()メンバー:

void clear() {
    this->~vector();
}

このようなデストラクタを直接呼び出すことは決して決してしないでください。コンパイラはデストラクタコードにあらゆる種類の他のコードを置くことができます。なぜなら、それがそれを呼び出す唯一のコードであることを「知っている」からです。たとえば、いくつかのコンパイラは、破壊後にヒープからオブジェクトを delete するかどうかを示すフラグを事前にプッシュします。あなたはそれをしていないので、上記のことはあらゆる種類のものを壊すことができます。

代わりに、デストラクタにあるコードを clear()に移動し、デストラクタから clear()を呼び出します。

もう1つは、これが@Nick Gammonによってピックアップされたものです:

bool isempty() {
    return !this->__data ||
           !this->capacity ||
           !this->size;
}

これは、 __ data メンバーが偽であるかどうか、 capacity size 関数が定義されているかどうかをテストしています。後者の2つは... __ の接頭辞が間違っています...

[Also, stop it with the this-> everywhere. You've used a __ before all your member variables (which is itself against convention); you don't need to hammer home the fact that they are member variables: the compiler already knows.]

2
追加された
私は vector(T obj、int count = 1)コンストラクタ全体を削除しました。エラーなしでコンパイルされていますので、この場合は問題ではありませんでしたが、 code>は初期化されていません。
追加された 著者 Nick Gammon,
は決してコンパイルしてはいけません - 関数を呼び出すのではなく、関数をテストしました。 capacity はメンバ関数であり、変数ではありません。私はそれを clang の下でコンパイルしようとしたときにそれを選択しました。
追加された 著者 Nick Gammon,
あなたは大丈夫です - 例えば、 ManiacbugのStandardCplusplus をご覧ください。しかしこの場合、私は答えをクラス作成のチュートリアルにしたいと思っていました。
追加された 著者 Nick Gammon,
ヒッヒッヒ!私はそれを逃した - あなたは正しい!
追加された 著者 makons,
アドバイスのビット:あなたのコードは事実上判読できません。優れたコーディングスタイルを使用する: google.github.io/styleguide/cppguide.html
追加された 著者 Geremia,
私はまた、なぜあなたがこれをやっているのか分からない。本格的なSTLはサポートされていませんが、シンプルなGoogle検索で見つけることができるよくデバッグされた実装がいくつかあります。あなたがC ++を学ぶためにそれをやっているなら、arduino環境はおそらくそれを行う最悪の方法です!
追加された 著者 Geremia,
私は多くの情報に慣れていませんでした。私は他の場所で始めるべきだった。とにかく、それらの間違いを指摘してくれてありがとう、それは自分で見つけるために年を取っていただろう。
追加された 著者 ritowrongo,

コードには多くの問題があったので、どこから始めるべきかを知ることは難しいです。 :)

私はジョン・バーガーが言ったことに同意する。

私はあなたのコードを私のPCにアップロードして毎回それをアップロードするのをやめ、それにvalgrindを使うことができました。確かにあなたがベクトルを印刷した後、valgrindはエラーを報告しました。その理由は簡単です。クラスのコピー print_vec に渡しました。つまり、 print_vec の終了時にデストラクタが呼び出され、メモリの割り当てが解除されました。あなたはコピーコンストラクタを持っていたはずです。それがなければ、コンパイラはクラスのビット単位のコピーを作成します。つまり、割り当てられた同じメモリを共有する2つのオブジェクトがあります。

迅速かつ厄介な修正は、 print_vec を参照して呼び出すことです。

void print_vec(vector & v){

しかし、これは未来のためにバグを潜んでいる。

下の例ではコピーコンストラクタを実装しましたが、参照によって print_vec を呼び出すと、コピーするクラスが保存され、実行中の new/delete メモリの断片化を軽減する可能性があります。


John Burgerは次のように述べています。デストラクタをあなた自身に呼んではいけません!そうすることはできません。デストラクタと clear 関数で同じことをしたい場合は、 clear()を呼び出すデストラクタを取得してください。


The use of leading double-underscores in variables is contrary to the C++ Standard. Don't do that. Use a trailing underscore if you want to indicate a member variable. Lose all the this-> references. They just clutter things up.


配列を割り当てるので、 delete [] を使うべきです。


なぜこれを行うのですか?これは不要な割り当てです。

  vector i = vector();

ちょうど:

  vector i;

何らかの理由でクラスを割り当てる場合は、 operator = も実装する必要があります。そうしないと、コピーコンストラクタの場合と同じ問題が発生します。 (私の例を見てください)。


ここでは、関数を呼び出さずに関数(容量とサイズ)をテストします。

bool isempty() {
    return !this->__data ||
        !this->capacity ||
        !this->size;
}

あなたは本当にこれを行うことはできません:

  T const & operator[] (unsigned int idx) const {
        if (idx < this->__count) {
            return this->__data[idx];
        }
        else {
           //throw exception or handle error to be implemented
        }
    }

"else"パスを指定すると、関数は値を返しません(警告をオンにすると、コンパイラの警告が表示されます)。


私の修正された例、その中に私の提案があります。それは警告やエラーなしでコンパイルされ、クラッシュすることなく実行されます(PC上)。

//      g++ -std=c++11 -g3 test.cpp -o test
// OR:  clang -std=c++11 -g3 -Wall test.cpp -lstdc++ -o test
//
// valgrind --leak-check=yes ./test

#include      //printf
#include      /* memcpy */

#define min(a,b) (a < b) ? a : b

template 
class vector {
public:
    //empty constructor
    vector() : capacity_(0), count_(0), data_(nullptr) {}

   //copy constructor
    vector (const vector & rhs) : capacity_(0), count_(0), data_(nullptr)
      {
      data_ = new T [rhs.capacity_];
      capacity_ = rhs.capacity_;
      count_ = rhs.count_;
      memcpy (data_, rhs.data_, sizeof (T) * count_);
      } //end of copy constructor

   //operator=
    vector & operator= (const vector & rhs)
      {
     //gracefully handle self-assignment (eg. a = a;)
      if (this == &rhs )
        return *this;

      data_ = new T [rhs.capacity_];
      capacity_ = rhs.capacity_;
      count_ = rhs.count_;
      memcpy (data_, rhs.data_, sizeof (T) * count_);
      return *this;
      } //end of operator=

   //destructor
    ~vector()
      {
        clear ();
      } //end of destructor

    T const & operator[] (unsigned int idx) const {
       return data_[idx];
    }

    T& operator[] (unsigned int idx) {
       return data_[idx];
    }

    void resize_to_fit() {
        resize(count_);
    }

    T& pop_back(){
        return data_[--count_];
    }

    void push_back(T obj) 
        {
        if (capacity_ == count_) {
            resize(capacity_ + 1);
        }

        data_[count_++] = obj;
    }

    bool isempty() {
        return count_ == 0;
    }

    void clear() {
      delete [] data_;
      data_ = nullptr;
      count_ = capacity_ = 0;
    }

    T* data() {
        return data_;
    }

    int size() {
        return count_;
    }

    int capacity() {
        return capacity_;
    }
private:
    void resize(int capacity) {
        if (data_ == nullptr) {
            data_ = new T[capacity];
            count_ = 0;
            capacity_ = capacity;
        }
        else if (capacity > capacity_) { //else do nothing

           //allocate new memory
            T* data = new T[capacity];
           //count can't be higher than capacity
            count_ = min(count_, capacity);
            capacity_ = capacity;
           //copy data across to new pointer
            memcpy(data, data_, sizeof(T) * count_);
           //delete old pointer
            delete [] data_;
           //now remember the new pointer
            data_ = data;
        }
    }
protected:

        int capacity_;
        int count_;
        T* data_;
};



void print_vec(vector v){
  printf("%s\n", "Printing new vec!");
  for( int i = 0; i < v.size(); i++){
    printf("%i\n", v[i]);
  }
}

int main() {

  vector i;

  i.push_back(10);
  i.push_back(2);
  print_vec(i);
  i.push_back(3);

  vector j;

 //test assignment
  j = i;
  print_vec(j);
  print_vec(i);


  while(i.size() > 0){
    print_vec(i);
    i.pop_back();
  }


}
1
追加された
さて、ありがとう!多くの問題は私の終わりに気を散らしていました。私は最初に直面していた記憶の問題を解決することに焦点を当てていました。
追加された 著者 ritowrongo,

私が追加したいことがあります。
他の人はすでにコード内に多数のエラーを指摘しています。そして私はもっと多くを指すことができますが、それはポイントではありません。
IMO、最大の間違いはArduinoのVectorクラスを実装することです!
それが動作しても、それはちょうど良い考えです。 Arduinoは非常に限られたメモリ量しか持っていないことに注意してください。このような限られた空間での動的メモリ割り当ては、悪い考えのように聞こえます。メモリを浪費しています。ヒープを断片化していて、何らかのコーディングの便宜を除いてほとんど何も得られません。
私はArduinoプロジェクトで動的メモリ割り当てが本当に必要なケースをまだ見ていません。そして、たとえあなたが使っていても、(スタック割り当ての一時バッファ、アリーナアロケータ、プールアロケータなどの)多くのツールを使用できます。しかし、おそらく、これらの「汎用」ソリューションのコードとメモリのコストは、Arduinoでは実現不可能になるでしょう。
たいていの場合、ベクトル、地図などをPCのようなプラットフォームで使うのは大丈夫です(私はPCゲームプログラマーとして働くので、これらのエクストラは大きな「ノー」ですが、ほとんどのアプリケーションでは)。
しかし、Arduinoのような「タイトな」プラットフォームでは、「金属に近い」状態を維持し、何が起こっているのかを完全に制御する必要があると思います(メモリとCPUサイクルが賢明です)。理想的には、「下に」何が起こっているのかを知っているときには、これらのような「贅沢」しか用意できません。 Arduinoのような "タイトな"プラットフォームではまれなケースです。

0
追加された
ここにポイントがあります。特に、メモリを増分的に割り当てる(通常は4GbのRAMを搭載したPCでうまくいく) push_back という考え方は、メモリの断片化を招く可能性があります。私は、通常のSTL実装でも、この問題を軽減するために、バッチ(例:10回)でベクトルメモリ(この場合は容量)を割り当てていると思います。
追加された 著者 Nick Gammon,