値型

7 12月

Javaと.NETの大きな違いとして、値型とジェネリックの扱いの差があります。

昨日の予告通り、今日はこの値型について説明します(ジェネリックはまた明日にでも)。

Javaと.NETの値型

Javaと.NETでは、値型に関して以下のような差があります。

Java:

  • 組み込みの整数型(intなど)だけが値型です
  • intなどを参照渡ししたい場合には、Integerクラスなど、ラッパー クラスを通す必要があります
    • Javaの自動ボクシングは、このラッパー クラスへの自動変換です

.NET:

  • 組み込みの整数型は値型です
  • 列挙型も値型です
  • ユーザー定義型にも、値型と参照型の区別があります
    • C#の場合、classは参照型で、structは値型になります

要は、Javaではユーザー定義の値型を作れません。位置やベクトルのように、サイズが小さく、継承も必要ないようなオブジェクトは、値型として扱いたいですが、Javaではそれが許されません。

仮想マシン レベルでの仕様の問題なので、言語レベルでの解決もできなければ、実装の洗練で性能改善もできないです。また、Javaだけでなく、Java仮想マシン上に構築したすべての言語で同様の問題を抱えることになります。

値型と参照型

以下、「値型って何?」という人向けの説明。

サンプルとして、以下のようなコードを見てみましょう。

using System;

class CPoint { public int X, Y; }
struct SPoint { public int X, Y; }

class Program
{
    static void Main()
    {
        var c = new CPoint();
        Write(c);

var s = new SPoint();
        Write(s);
    }

static void Write(dynamic p)
    {
        Console.WriteLine("(" + p.X + ", " + p.Y + ")");
    }
}

classstructかという違いしかない、似たような型CPointSPointを作っています。

C++的にいうと

この違いですが、C++がわかる人には、以下のようなコードを見ていただければなんとなくわかるかと思われます。

#include<iostream>
#include<cstring>

struct Point
{
    int X, Y;
};

int main()
{
    // C#

struct (前例の SPoint)はこういう扱い。
    Point s;
    std::memset(&s, 0, sizeof(Point));
    std::cout << s;

// C#

class CPoint)はこういう扱い。
    Point* c;
    c = new Point();
    std::cout << *c;
    delete c;

return 0;
}

std::ostream& operator<< (std::ostream& os, Point p)
{
    os << "(" << p.X << ", " << p.Y << ")";
    return os;
}

要するに、以下のような差です。

値型:

  • 変数は、スタック上に領域を確保します
  • コピー渡しになります

参照型:

  • 変数は、ヒープ上に領域を確保します
  • 参照渡し(型安全なポインター)になります
  • C#の場合は、ガベージ コレクションが自動管理しているので、deleteは不要です

C++の場合、同じ型を値としても参照としても使えますが、C#の場合には、structは必ず値、classは必ず参照になります。

継承や仮想メソッドを使う場合、参照を使わないとうまく動かないんですが、C++の場合はそれではまる初心者も多いかと思います。Javaがユーザー定義型の値型を認めていないのもそのためです。一方、C#では、継承可能な参照型としてclassを、継承できない値型としてstructを別途用意しています。

図で表すと

例えば、以下のようなコードを書いたとします。Pointの部分には、上記のSPointCPointのいずれかが入ると思ってください。別途、+ 演算子を用意したとします。

var p = new Point { X = 1, Y = 2 };
var sum = new Point();

for (int i = 0; i < 2; i++)
{
    sum += p;
}

Pointが値型か参照型かによって、利用するメモリに関して、以下の図のような差が起こります。

参照型で下手な作り方をすると、ヒープ上に大量のごみを作ってしまい、ガベージ コレクションを頻発させ、性能を大きく落とします。

もう2例ほど見てみましょう。まず1つ目。以下のようなコードを考えます。

var x = new
{
    C1 = new CPoint { X = 0, Y = 1 },
    C2 = new CPoint { X = 1, Y = 0 },
    S1 = new SPoint { X = 0, Y = 1 },
    S2 = new SPoint { X = 1, Y = 0 },
};

SPointCPointを包含する別の型を作っています。この時、メモリ使用状況的には以下のようになります。

もう1つは、配列を作ってみましょう。

var s1 = new[]
{
    new SPoint { X=1, Y=2 },
    new SPoint { X=2, Y=1 },
    new SPoint { X=3, Y=0 },
};

var c1 = new[]
{
    new CPoint { X=1, Y=2 },
    new CPoint { X=2, Y=1 },
    new CPoint { X=3, Y=0 },
};

この場合はs1(値型)とc1(参照型)で以下のような差が生じます。

最後の2つの例を見ての通り、参照型を使うと、1段階余計な間接参照が起こります。オブジェクトのサイズが小さく、読み書きの頻度が高い場合、無視できないコストになります。

広告

コメント / トラックバック2件 to “値型”

  1. tet (@tet_rw) 2011/12/07 @ 15:43 #

    for (int i = 0; i < 2; i++)
    {
    sum *= p;
    }

    +=?

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。