第15問の答え

答え

C++では、mallocの戻り値はそのままだと使えない!
  Triangle *triangle = malloc(sizeof(Triangle));
こうやってキャストするのが正解!
  Triangle *triangle = static_cast<Triangle *>(malloc(sizeof(Triangle)));
または、C言語方式のキャストでもOK!
  Triangle *triangle = (Triangle *)malloc(sizeof(Triangle));

解説

まず、C++はC言語の上位互換ではないということを知っておきましょう。C言語のプログラムは大部分がC++としても正しいプログラムといえますが、例外もあるのです。

今回の問題は、void *の扱いがC言語とC++とで異なることに起因しています。malloc()の戻り値はvoid *なので、そのままC++のプログラムとしてコンパイルすることはできません。

C言語では、こうなります。

  • 任意の型のアドレスは、void *型のポインタに代入できる
  • void *型のアドレスは、任意の型のポインタに代入できる

これに対して、C++ではこうです。

  • 任意の型のアドレスは、void *型のポインタに代入できる(C言語と同じ)
  • void *型のアドレスは、void *型以外のポインタには代入できない(C言語と違う)

これは、危険な(タイプセーフではない)代入を抑制するためのルールです。とはいえ、コンパイラは開発者を信頼することになっているので、明示的にキャストすればコンパイルエラーは発生しなくなります。

C++のキャストには種類がありますが、ここではstatic_castを使うのが適切です。

  Triangle *triangle = static_cast<Triangle *>(malloc(sizeof(Triangle)));
static_castはC++の文法よ。
キャストはC言語にもありますよね?
そうね。C言語と同じ方式のキャストを使ってもOKよ。

C++では、次のようにC言語方式のキャストも使用可能です。この書き方なら、同じソースファイルをC言語とC++どちらのコンパイラでコンパイルしてもエラーになりません。

  Triangle *triangle = (Triangle *)malloc(sizeof(Triangle));

今はC言語を使っているけれど、いずれはC++に移行するつもりだということもあるでしょう。その場合は、あらかじめ(C言語方式で)キャストしておけば、移行がスムーズになります。

なお、C言語とC++のソースファイルが混在したままで開発を進めることも可能です。それぞれのコンパイラでコンパイルしたものを、リンクする方法があるためです。既存のソースファイルはあくまでC言語として扱い、C++から呼び出す方針にするのでもよいでしょう。

ここがポイント!
C言語のソースファイルをC++として使うには、手直しが必要になる場合がある!

修正後のプログラム

main.cpp
#include <stdio.h>
#include <stdlib.h>

typedef struct {
  double base;
  double height;
} Triangle;

double TriangleArea(const Triangle *triangle) {
  return triangle->base * triangle->height / 2.0;
}

int main(void) {
  Triangle *triangle = static_cast<Triangle *>(malloc(sizeof(Triangle)));
  if (triangle) {
    triangle->base = 6.0;
    triangle->height = 4.0;

    printf("area = %f\n", TriangleArea(triangle));

    free(triangle);
    triangle = 0;
  }

  return EXIT_SUCCESS;
}
実行結果
area = 12.000000