web-dev-qa-db-ja.com

なぜクラスよりも構造体を選ぶのか

Javaのバックグラウンドから来たSwiftで遊んでみて、なぜクラスではなくStructを選びたいのですか?それらは同じものであるように思えますが、Structはより少ない機能を提供します。なぜそれを選ぶのですか?

444
bluedevil2k

非常に人気のあるWWDC 2015のSwiftでのプロトコル指向プログラミング( videotranscript )によると、Swiftは多くの状況で構造体をクラスよりも良くする多くの機能を提供します。

コピーがクラスで起こるように同じインスタンスへの複数の参照を持つよりはるかに安全であるので、それらが比較的小さくてコピー可能であるならば、構造体は好ましいです。これは、変数を多くのクラスに渡したり、マルチスレッド環境で渡したりする場合に特に重要です。あなたが常にあなたの変数のコピーを他の場所に送ることができるのであれば、あなたはその他の場所があなたの下のあなたの変数の値を変えることを心配する必要は決してありません。

Structsを使用すると、変数の単一のインスタンスにアクセスしたり変更したりするためのメモリリークや複数のスレッドの競合について心配する必要がはるかに少なくなります。 (もっと技術的なことを言うと、クロージャの中で構造体をキャプチャするときは例外です。なぜなら、明示的にコピーするようにマークしない限り、インスタンスへの参照をキャプチャしているからです)。

クラスは単一のスーパークラスからしか継承できないため、クラスも肥大化する可能性があります。それは私達が大まかに関連しているだけである多くの異なる能力を包含する巨大なスーパークラスを作成することを奨励します。プロトコルを使用すると、特にプロトコルに実装を提供できるプロトコル拡張機能を使用すると、このような動作を実現するためのクラスが不要になります。

この講演では、クラスが優先される次のシナリオについて説明します。

  • インスタンスをコピーまたは比較することは意味がありません(例:Window)
  • インスタンスの存続期間は、外部効果(TemporaryFileなど)に関連付けられています
  • インスタンスは単なる「シンク」 - 外部状態への書き込み専用コンジット(例:CGContext)

これは、構造体がデフォルトでクラスがフォールバックであるべきであることを意味します。

一方、 The Swift Programming Language のドキュメントは多少矛盾しています。

構造体インスタンスは常に値によって渡され、クラスインスタンスは常に参照によって渡されます。つまり、さまざまな種類の作業に適しています。プロジェクトに必要なデータ構造と機能を検討しながら、各データ構造をクラスとして定義するか構造として定義するかを決定します。

一般的なガイドラインとして、以下の条件が1つ以上当てはまる場合に構造を作成することを検討してください。

  • この構造の主な目的は、いくつかの比較的単純なデータ値をカプセル化することです。
  • その構造のインスタンスを代入または受け渡しするときに、カプセル化された値が参照されるのではなくコピーされることを期待するのは妥当です。
  • 構造体に格納されているプロパティはすべて値型であり、参照されるのではなくコピーされることも想定されます。
  • 構造体は、他の既存の型からプロパティや動作を継承する必要はありません。

構造の良い候補の例は次のとおりです。

  • 幾何学的図形のサイズ。おそらくwidthプロパティとheightプロパティをカプセル化します。どちらもDouble型です。
  • 系列内の範囲を参照する方法。おそらく、両方ともInt型のstartプロパティとlengthプロパティをカプセル化します。
  • それぞれがDouble型の、x、y、およびzプロパティをカプセル化している可能性がある3D座標系内の点。

それ以外の場合はすべて、クラスを定義し、そのクラスのインスタンスを作成して参照および参照で管理します。実際には、これはほとんどのカスタムデータ構成体が構造体ではなくクラスであるべきであることを意味します。

ここでは、特定の状況でのみクラスを使用し、構造を使用することをデフォルトとすべきだと主張しています。最終的には、値型と参照型の現実の関係を理解する必要があります。それから、いつ構造体またはクラスを使用するかについて十分な情報に基づいた決定を下すことができます。また、これらの概念は常に進化しており、Swift Programming Languageのドキュメントはプロトコル指向プログラミングの講演が行われる前に作成されたものであることにも注意してください。

510
drewag

構造体インスタンスはスタック上に割り当てられ、クラスインスタンスはヒープ上に割り当てられるため、構造体は時々劇的に速くなることがあります。

しかし、あなたはいつも自分でそれを測定し、あなたのユニークなユースケースに基づいて決めるべきです。

Intstructを使用してclassデータ型をラップする2つの方法を示す次の例を検討してください。複数のフィールドがある現実の世界をよりよく反映するために、10個の値を繰り返し使用しています。

class Int10Class {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

struct Int10Struct {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

func + (x: Int10Class, y: Int10Class) -> Int10Class {
    return IntClass(x.value + y.value)
}

func + (x: Int10Struct, y: Int10Struct) -> Int10Struct {
    return IntStruct(x.value + y.value)
}

パフォーマンスは

// Measure Int10Class
measure("class (10 fields)") {
    var x = Int10Class(0)
    for _ in 1...10000000 {
        x = x + Int10Class(1)
    }
}

// Measure Int10Struct
measure("struct (10 fields)") {
    var y = Int10Struct(0)
    for _ in 1...10000000 {
        y = y + Int10Struct(1)
    }
}

func measure(name: String, @noescape block: () -> ()) {
    let t0 = CACurrentMediaTime()

    block()

    let dt = CACurrentMediaTime() - t0
    print("\(name) -> \(dt)")
}

コードは https://github.com/knguyen2708/StructVsClassPerformance にあります。

UPDATE(2018年3月27日)

IPhone 6S、iOS 11.2.6でリリースビルドを実行しているSwift 4.0、Xcode 9.2、Swift Compilerの設定は-O -whole-module-optimizationです。

  • classバージョンは2.06秒かかりました
  • structバージョンは4.17e-08秒かかりました(50,000,000倍高速)

(分散が非常に小さいので、私はもはや複数回の実行を平均化していません、5%以下)

:モジュール全体を最適化しない限り、違いはそれほど劇的ではありません。誰かがフラグが実際に何をするのかを指摘できればうれしいです。


UPDATE(2016年5月7日)

Swift 2.2.1、Xcode 7.3、iPhone 6S、iOS 9.3.1でリリースビルドを実行し、平均5回の実行で、Swift Compiler設定は-O -whole-module-optimizationです。

  • classのバージョンは2.159942142sでした
  • structバージョンは5.83E-08秒(37,000,000倍高速)

:実際のシナリオでは、1つの構造体に1つ以上のフィールドが存在する可能性があると誰かが言ったように、1ではなく10フィールドの構造体/クラスのテストを追加しました。 tはさまざまです。


当初の結果 (2014年6月1日):

(10ではなく1つのフィールドを持つ構造体/クラスで実行されました)

Swift 1.2、Xcode 6.3.2、リリースビルドをiPhone 5S、iOS 8.3上で実行し、平均5回の実行

  • classバージョンの所要時間は9.788332333s
  • structバージョンの値は0.010532942秒(900倍高速)

古い結果 (未知の時間から)

(10ではなく1つのフィールドを持つ構造体/クラスで実行されました)

私のMacBook Proのリリースビルドでは:

  • classバージョンは1.10082秒かかりました
  • structバージョンは0.02324秒かかりました(50倍高速)
154
Khanh Nguyen

構造体とクラスの間の類似点.

私は簡単な例でこれのための要旨を作成しました。 https://github.com/objc-Swift/swift-classes-vs-structures

そして違い

1.継承.

構造はSwiftでは継承できません。お望みならば

class Vehicle{
}

class Car : Vehicle{
}

クラスに行きます。

2.通り過ぎる

Swift構造体は値渡し、クラスインスタンスは参照渡しです。

コンテキストの違い

構造体定数と変数

例(WWDC 2014で使用)

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

Pointという名前の構造体を定義します。

var point = Point(x:0.0,y:2.0)

今私はxを変更しようとした場合。その有効な表現です。

point.x = 5

しかし、もし私が点を定数として定義したら。

let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.

この場合、点全体は不変の定数です。

代わりにPointクラスを使用した場合、これは有効な式です。クラス内では不変定数はそのインスタンス変数ではなくクラス自体への参照であるため(これらの変数が定数として定義されている場合を除く)

60
MadNik

考慮すべきその他の理由は次のとおりです。

  1. 構造体は自動初期化子を取得するので、コード内でメンテナンスする必要はまったくありません。

    struct MorphProperty {
       var type : MorphPropertyValueType
       var key : String
       var value : AnyObject
    
       enum MorphPropertyValueType {
           case String, Int, Double
       }
     }
    
     var m = MorphProperty(type: .Int, key: "what", value: "blah")
    

これをクラスで取得するには、イニシャライザと maintain イニシャライザを追加する必要があります。

  1. Arrayのような基本的なコレクション型は構造体です。あなた自身のコードでそれらを使うほど、参照ではなく値渡しに慣れるでしょう。例えば:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
    
  2. 不変性と可変性は明らかに大きなトピックですが、多くの頭の良い人は不変性(この場合は構造体)が望ましいと考えています。 可変オブジェクトと不変オブジェクト

28
Dan Rosenstark

Structvalue typeで、Classreference typeであることがわかっているとします。

値型と参照型が何であるかわからない場合は、 参照渡しと値渡しの違いは何ですか?を参照してください。

mikeashの投稿 に基づく。

...まず、極端な、明白な例を見てみましょう。整数は明らかにコピー可能です。それらは値型であるべきです。ネットワークソケットを賢くコピーすることはできません。それらは参照型であるべきです。 x、yペアのように、点はコピー可能です。それらは値型であるべきです。ディスクを表すコントローラは、賢明にはコピーできません。それは参照型になるはずです。

いくつかのタイプはコピーすることができますが、それはあなたがいつも起こりたいことではないかもしれません。これはそれらが参照型であるべきであることを示唆しています。たとえば、画面上のボタンを概念的にコピーすることができます。コピーはオリジナルと全く同じにはなりません。コピーをクリックしてもオリジナルはアクティブになりません。コピーは画面上の同じ場所を占めることはありません。ボタンを渡したり、新しい変数に追加したりする場合は、元のボタンを参照することをお勧めします。明示的に要求されたときにだけコピーを作成したいと思うでしょう。つまり、ボタンの種類は参照型にする必要があります。

ビューとウィンドウコントローラも同様の例です。おそらくコピー可能かもしれませんが、やりたいことがほとんどないのです。それらは参照型であるべきです。

モデルタイプはどうですか?システム上のユーザーを表すユーザータイプ、またはユーザーが行った行動を表す犯罪タイプがあります。これらはかなりコピー可能なので、おそらく値型であるべきです。ただし、プログラム内の1か所で行われたユーザーの犯罪に対する更新を、プログラムの他の部分からも見えるようにしたい場合があります。 これはあなたのユーザが参照型となるある種のユーザコントローラによって管理されるべきであることを示唆しています。例えば

struct User {}
class UserController {
    var users: [User]

    func add(user: User) { ... }
    func remove(userNamed: String) { ... }
    func ...
}

コレクションは興味深いケースです。これらは文字列だけでなく配列や辞書のようなものも含みます。それらはコピー可能ですか?明らかにあなたが簡単にそして頻繁に起こりたいものをコピーしていますか?それははっきりしません。

ほとんどの言語はこれに「いいえ」と言って、それらのコレクションを参照型にします。これはObjective-CとJava、PythonとJavaScript、そして私が考えることができる他のほとんどすべての言語に当てはまります。 (1つの大きな例外はSTLコレクション型のC++ですが、C++は言語の世界の中でも非常に奇妙なことをしているのです。)

Swiftは「はい」と言っています。つまり、ArrayやDictionary、Stringなどの型はクラスではなく構造体です。それらは代入時に、そしてパラメータとしてそれらを渡す時にコピーされます。コピーが安価である限り、これは完全に賢明な選択です。 ...

さらに、関数のすべてのインスタンスをオーバーライドする必要がある場合、つまりshared機能を持たない場合は、classを使用しないでください。

それで、クラスのいくつかのサブクラスを持つ代わりに。プロトコルに準拠したいくつかの構造体を使用してください。

20
Honey

いくつかの利点:

  • 共有できないため自動的にスレッドセーフ
  • isaとrefcountがないため、メモリの使用量が少なくて済みます(実際、スタック割り当てが一般的です)。
  • メソッドは常に静的にディスパッチされるため、インライン化することができます(@finalはクラスに対してこれを実行できます)。
  • スレッドセーフと同じ理由で、(NSArray、NSStringなどでは一般的であるように「防御的にコピーする」必要はありません)について考えるのは簡単です。
18
Catfish_Man

構造はClassよりはるかに速いです。また、継承が必要な場合はClassを使用する必要があります。最も重要な点は、Classが参照型であるのに対し、Structureは値型であるということです。例えば、

class Flight {
    var id:Int?
    var description:String?
    var destination:String?
    var airlines:String?
    init(){
        id = 100
        description = "first ever flight of Virgin Airlines"
        destination = "london"
        airlines = "Virgin Airlines"
    } 
}

struct Flight2 {
    var id:Int
    var description:String
    var destination:String
    var airlines:String  
}

両方のインスタンスを作成しましょう。

var flightA = Flight()

var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )

これらのインスタンスを、ID、説明、宛先などを変更する2つの関数に渡します。

func modifyFlight(flight:Flight) -> Void {
    flight.id = 200
    flight.description = "second flight of Virgin Airlines"
    flight.destination = "new york"
    flight.airlines = "Virgin Airlines"
}

また、

func modifyFlight2(flight2: Flight2) -> Void {
    var passedFlight = flight2
    passedFlight.id = 200
    passedFlight.description = "second flight from virgin airlines" 
}

そう、

modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)

これで、flightAのIDと説明を印刷すると、次のようになります。

id = 200
description = "second flight of Virgin Airlines"

ここでは、modifyメソッドに渡されたパラメータが実際にflightAオブジェクトのメモリアドレス(参照型)を指しているため、FlightAのIDと説明が変更されていることがわかります。

fLightBインスタンスのIDと説明を表示すると、

id = 100
description = "first ever flight of Virgin Airlines"

ここで、FlightBインスタンスは変更されていないことがわかります。これは、modifyFlight2メソッドでは、参照(値型)ではなく実際のFlight2のインスタンスが渡されるためです。

12
Manoj Karki

値タイプと参照タイプの観点から質問に答えると、 このAppleのブログ投稿 /それは非常に単純に見えるでしょう。

値型を使用する構造体、列挙型]

  • インスタンスデータと==を比較することは理にかなっています
  • あなたはコピーに独立した状態を持たせたい
  • データは複数のスレッドにわたるコードで使用されます

参照型を使用するいつ]

  • インスタンスのアイデンティティと===を比較することは理にかなっています
  • 共有された可変状態を作成したい

その記事で述べたように、書き込み可能なプロパティを持たないクラスは、1つ注意を払って(追加します)、structと同じように振る舞います:structは スレッドセーフなモデルに最適です アプリのアーキテクチャ.

4
David James

あなたが継承を得て、参照によって渡されるクラスでは、構造体は継承を持たず、値によって渡されます。

Swiftには素晴らしいWWDCセッションがあります。この特定の質問は、そのうちの1つで詳細に回答されています。言語ガイドやiBookよりもはるかに速くスピードアップできるようになるので、必ずそれらを見てください。

3
Joride

構造体が提供する機能が少ないとは言えません。

もちろん、selfは変化する関数を除いて不変ですが、それだけです。

すべてのクラスは抽象クラスまたは最終クラスのどちらかでなければならないという古き良き考えに固執する限り、継承はうまく機能します。

抽象クラスをプロトコルとして実装し、最後のクラスを構造体として実装します。

構造体についての素晴らしいところは、コピーオンライトがそれを処理するので、共有可変状態を作成せずにフィールドを可変にできることです。

そのため、次の例のプロパティ/フィールドはすべて可変になっています。JavaやC#、Swift classes にはありません。

"example"という名前の関数の下部に、少し汚れた、わかりやすい使い方をした継承構造の例:

protocol EventVisitor
{
    func visit(event: TimeEvent)
    func visit(event: StatusEvent)
}

protocol Event
{
    var ts: Int64 { get set }

    func accept(visitor: EventVisitor)
}

struct TimeEvent : Event
{
    var ts: Int64
    var time: Int64

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }
}

protocol StatusEventVisitor
{
    func visit(event: StatusLostStatusEvent)
    func visit(event: StatusChangedStatusEvent)
}

protocol StatusEvent : Event
{
    var deviceId: Int64 { get set }

    func accept(visitor: StatusEventVisitor)
}

struct StatusLostStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var reason: String

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

struct StatusChangedStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var newStatus: UInt32
    var oldStatus: UInt32

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

func readEvent(fd: Int) -> Event
{
    return TimeEvent(ts: 123, time: 56789)
}

func example()
{
    class Visitor : EventVisitor
    {
        var status: UInt32 = 3;

        func visit(event: TimeEvent)
        {
            print("A time event: \(event)")
        }

        func visit(event: StatusEvent)
        {
            print("A status event: \(event)")

            if let change = event as? StatusChangedStatusEvent
            {
                status = change.newStatus
            }
        }
    }

    let visitor = Visitor()

    readEvent(1).accept(visitor)

    print("status: \(visitor.status)")
}
2
yeoman

Swiftでは、プロトコル指向プログラミングと呼ばれる新しいプログラミングパターンが導入されました。

作成パターン:

Swiftでは、Structは 値型 で、自動的に複製されます。したがって、プロトタイプパターンを無料で実装するために必要な動作が得られます。

classes は参照型ですが、代入時に自動的に複製されることはありません。プロトタイプパターンを実装するために、クラスはNSCopyingプロトコルを採用しなければなりません。


シャローコピー はそれらのオブジェクトを指す参照のみを複製しますが、 ディープコピー はオブジェクトの参照を複製します。


ディープコピー をそれぞれの 参照型 に実装するのは面倒な作業になりました。クラスにさらに参照型が含まれる場合は、各参照プロパティに対してプロトタイプパターンを実装する必要があります。それからNSCopyingプロトコルを実装することによってオブジェクトグラフ全体を実際にコピーしなければなりません。

class Contact{
  var firstName:String
  var lastName:String
  var workAddress:Address // Reference type
}

class Address{
   var street:String
   ...
} 

structsとenums を使用することで、コピーロジックを実装する必要がないので、コードをよりシンプルにしました。

2
Balasubramanian

Structsvalue typeで、Classesreference typeです

  • 値型は参照型より速い
  • マルチスレッド環境では、競合状態やデッドロックを気にせずに複数のスレッドがインスタンスを変更できるため、値型インスタンスは安全です。
  • 参照型とは異なり、値型には参照がありません。したがって、メモリリークはありません。

次の場合はvalue型を使用してください。

  • あなたはコピーが独立した状態を持つことを望みます、データは複数のスレッドにわたるコードで使われるでしょう

次の場合はreference型を使用してください。

  • あなたは共有された可変状態を作りたいのです。

詳しい情報はAppleのドキュメントにもあります。

https://docs.Swift.org/Swift-book/LanguageGuide/ClassesAndStructures.html


追加情報

高速値タイプはスタックに保持されます。プロセス内では、各スレッドは独自のスタックスペースを持っているため、他のスレッドが自分の値型に直接アクセスすることはできません。したがって、競合状態、ロック、デッドロック、または関連するスレッド同期の複雑さはありません。

値型は動的なメモリ割り当てや参照カウントを必要としません。どちらも高価な操作です。同時に値型のメソッドは静的にディスパッチされます。これらは、パフォーマンスの点で値型を優先するという大きな利点を生み出します。

念のため、Swiftのリストをここに示します。

値のタイプ:

  • 構造体
  • 列挙型
  • タプル
  • プリミティブ(Int、Double、Boolなど)
  • コレクション(配列、文字列、辞書、集合)

参照タイプ:

  • クラス
  • NSObjectから来るものすべて
  • 関数
  • 閉鎖
2
casillas

多くのCocoa APIはNSObjectサブクラスを必要とします。ただし、それ以外に、AppleのSwiftブログの以下のケースを使用して、構造体/列挙型またはクラス参照型のどちらを使用するかを決定できます。

https://developer.Apple.com/Swift/blog/?id=10

1
akshay

構造体は値型なので、スタックに格納するメモリを非常に簡単に作成できます。構造体は簡単にアクセスでき、作業の範囲を超えた後はスタックの一番上からpopを通じてスタックメモリから簡単に割り当て解除されます。一方、classはヒープに格納する参照型であり、1つのクラスオブジェクトに加えられた変更は密接に結合され参照型として他のオブジェクトに影響を与えます。 。

構造体の不利な点は、それを継承できないことです。

0
Tapash Mollick

これらの答えで注目されていない1つの点は、クラスを保持する変数と構造体を保持する変数はletになり得ますが、それでもオブジェクトのプロパティを変更することはできますが、構造体ではできません。

これは、変数が他のオブジェクトを指し示したくないが、それでもオブジェクトを変更する必要がある場合、つまり、次々に更新したいインスタンス変数が多数ある場合に役立ちます。それが構造体である場合、Swiftの定数値型は正しくゼロ変換を許可しますが、参照型(クラス)はこのように動作しないため、これを行うにはvarを使用して変数を別のオブジェクトにリセットできます。 。

0
johnbakers