2.2.3. 割り当てた配列の情報を構造体に記録する例

今までは割り当てたメモリ領域の先頭アドレスだけをポインタ変数で保持していました が、配列領域の長さも変数で記録しておくと便利なことがあります。ポインタと長さを セットで覚えておくために、構造体かクラスを使うことができます。

この例では、歴史的に古くからある構造体を用いて、C言語風に実装しています。

 1// s003: dynamically allocated array held on a struct
 2// along with the data count.
 3//
 4#include <iostream>
 5#include <cassert>
 6
 7const int N_SIZE = 10;
 8
 9struct Array {
10    double *pdata;
11    int n_size;
12};

まず構造体 Array を定義しています。 Array という型がこれで定義されるの で、 Array 型の変数を定義できるようになります。

14// same functions as s001.cpp
15
16// function to set dummy data to the array
17void set_dummy_data_struct(Array *par)
18{
19    for (int i = 0; i < par->n_size; i++) {
20        par->pdata[i] = static_cast<double>(i) / static_cast<double>(par->n_size);
21    }
22}

ダミーデータを設定する set_dummy_data_struct 関数では Array型のポインタを受 け取ります。このポインタが指している Array のデータ中に、配列データのポイン タと、配列の要素数が記録されています。

par->n_size のようにして、ポインタが指している構造体のメンバ変数にアクセスします。

24void copy_data_struct(Array *pdest, const Array *psrc)
25{
26    assert(pdest->n_size == psrc->n_size);
27    for (int i = 0; i < pdest->n_size; i++) {
28        pdest->pdata[i] = psrc->pdata[i];
29    }
30}

データをコピーする関数ではArray型のポインタを2つ受け取ります。

コピー元の psrc には const がついています。そのため psrc が指している構 造体のメンバ変数は代入禁止になります。ただし、そこからさらに先は代入禁止ではあり ません。

  • psrc->n_size : 代入禁止

  • psrc->pdata : 代入禁止

  • psrc->pdata[i] : 代入可能

これでは、constの意味が不完全です。本来は「コピー元の psrc の中身は改変しな い」という約束のためにconstを付けているのに、配列の中身のデータについては書き換 えることができてしまいます。

この点はC++の言語仕様で割り切られている点だと思います。これでも一定の意義は果た せます。

32void print_data_struct(const Array *par)
33{
34    std::cout << "[";
35    for (int i = 0; i < par->n_size; i++) {
36        std::cout << par->pdata[i];
37        if (i < par->n_size - 1) {
38            std::cout << ", ";
39        }
40    }
41    std::cout << "]\n";
42}
43
44void print_data(const double *d, int n)
45{
46    std::cout << "[";
47    for (int i = 0; i < n; i++) {
48        std::cout << d[i];
49        if (i < n - 1) {
50            std::cout << ", ";
51        }
52    }
53    std::cout << "]\n";
54}

配列の内容を印字する print_data は、 Array を受け取る版と、配列の先頭ア ドレスを受け取る版の2つを用意しています。後でmain関数を見ると、それぞれを呼び出 す処理が登場します。

56// allocate an array.
57// returns true on success, false on failure.
58bool allocate_array_struct(int n, Array **ppArray) {
59    assert(ppArray != nullptr);
60    try {
61        Array *pArray = new Array;
62        pArray->pdata = new double[n];
63        pArray->n_size = n;
64        *ppArray = pArray;
65        return true;
66    } catch (const std::bad_alloc &e) {
67        // could not allocate memory
68        return false;
69    }
70}
71
72void free_array_struct(Array *pArray) {
73    delete [] pArray->pdata;
74    delete pArray;
75}

配列の割り当ては allocate_array_struct 関数で行っています。引数の **ppArrayArray 型のポインタのポインタです。処理は、 Array のメモ リ割り当てと、その先の配列データのメモリ割り当ての2段構えになっています。

free_array_struct では2段構えでメモリを解放しています。

77int main(int argc, const char *argv[])
78{
79    std::cout << "s003\n";
80
81    // allocate array dynamically on the heap area.
82    Array *parray1;
83    Array *parray2;
84    if (! allocate_array_struct(N_SIZE, &parray1)) {
85        return EXIT_FAILURE;
86    }
87    if (! allocate_array_struct(N_SIZE, &parray2)) {
88        free_array_struct(parray2);
89        return EXIT_FAILURE;
90    }

main 関数では配列を割り当てた後に個々の関数を呼んでいます。

allocate_array_struct 関数の呼び出しにおいては Array のポインタ変数 parray に対して &parray と書いて変数のアドレスを取得しています。「変数 parrayのアドレスはここだから、結果をこのアドレスに書き込んでください」というよう に引数として渡しています。

92    // 静的領域のメモリの初期値は 0
93    // ただし自分で初期値を入れることが推奨される。
94    std::cout << "Before initialization\n";
95    print_data_struct(parray1);
96
97    // 配列を直接受け取る関数を呼ぶ例
98    print_data(parray1->pdata, parray1->n_size);

配列の先頭アドレスを要求する関数 print_data を呼ぶときは、 Array の先頭 アドレスではなくてその先の配列データの先頭アドレス parray1->parray を渡して います。

100    set_dummy_data_struct(parray1);
101    std::cout << "After initialization\n";
102    print_data_struct(parray1);
103
104    copy_data_struct(parray2, parray1);
105    std::cout << "After array copy\n";
106    print_data_struct(parray2);
107
108    // return allocated memory to OS
109    // new に成功した場合にのみ delete を呼ぶこと。
110    free_array_struct(parray1);
111    free_array_struct(parray2);
112
113    return EXIT_SUCCESS; // exit status code 0
114}

ソース全体は以下のようになります:

  1// s003: dynamically allocated array held on a struct
  2// along with the data count.
  3//
  4#include <iostream>
  5#include <cassert>
  6
  7const int N_SIZE = 10;
  8
  9struct Array {
 10    double *pdata;
 11    int n_size;
 12};
 13
 14// same functions as s001.cpp
 15
 16// function to set dummy data to the array
 17void set_dummy_data_struct(Array *par)
 18{
 19    for (int i = 0; i < par->n_size; i++) {
 20        par->pdata[i] = static_cast<double>(i) / static_cast<double>(par->n_size);
 21    }
 22}
 23
 24void copy_data_struct(Array *pdest, const Array *psrc)
 25{
 26    assert(pdest->n_size == psrc->n_size);
 27    for (int i = 0; i < pdest->n_size; i++) {
 28        pdest->pdata[i] = psrc->pdata[i];
 29    }
 30}
 31
 32void print_data_struct(const Array *par)
 33{
 34    std::cout << "[";
 35    for (int i = 0; i < par->n_size; i++) {
 36        std::cout << par->pdata[i];
 37        if (i < par->n_size - 1) {
 38            std::cout << ", ";
 39        }
 40    }
 41    std::cout << "]\n";
 42}
 43
 44void print_data(const double *d, int n)
 45{
 46    std::cout << "[";
 47    for (int i = 0; i < n; i++) {
 48        std::cout << d[i];
 49        if (i < n - 1) {
 50            std::cout << ", ";
 51        }
 52    }
 53    std::cout << "]\n";
 54}
 55
 56// allocate an array.
 57// returns true on success, false on failure.
 58bool allocate_array_struct(int n, Array **ppArray) {
 59    assert(ppArray != nullptr);
 60    try {
 61        Array *pArray = new Array;
 62        pArray->pdata = new double[n];
 63        pArray->n_size = n;
 64        *ppArray = pArray;
 65        return true;
 66    } catch (const std::bad_alloc &e) {
 67        // could not allocate memory
 68        return false;
 69    }
 70}
 71
 72void free_array_struct(Array *pArray) {
 73    delete [] pArray->pdata;
 74    delete pArray;
 75}
 76
 77int main(int argc, const char *argv[])
 78{
 79    std::cout << "s003\n";
 80
 81    // allocate array dynamically on the heap area.
 82    Array *parray1;
 83    Array *parray2;
 84    if (! allocate_array_struct(N_SIZE, &parray1)) {
 85        return EXIT_FAILURE;
 86    }
 87    if (! allocate_array_struct(N_SIZE, &parray2)) {
 88        free_array_struct(parray2);
 89        return EXIT_FAILURE;
 90    }
 91
 92    // 静的領域のメモリの初期値は 0
 93    // ただし自分で初期値を入れることが推奨される。
 94    std::cout << "Before initialization\n";
 95    print_data_struct(parray1);
 96
 97    // 配列を直接受け取る関数を呼ぶ例
 98    print_data(parray1->pdata, parray1->n_size);
 99
100    set_dummy_data_struct(parray1);
101    std::cout << "After initialization\n";
102    print_data_struct(parray1);
103
104    copy_data_struct(parray2, parray1);
105    std::cout << "After array copy\n";
106    print_data_struct(parray2);
107
108    // return allocated memory to OS
109    // new に成功した場合にのみ delete を呼ぶこと。
110    free_array_struct(parray1);
111    free_array_struct(parray2);
112
113    return EXIT_SUCCESS; // exit status code 0
114}

Download the source code

実行例を示します:

% ./s003
s003
Before initialization
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
After initialization
[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
After array copy
[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]