2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SwiftAdvent Calendar 2022

Day 6

変数がデフォルト値を持つstructではmemberwise initとcustom initで挙動が異なる

Last updated at Posted at 2022-12-05

そもそもcustom/memberwise initializerって?

swift.org参考

Memberwise Initializers for Structure Types

Structure types automatically receive a memberwise initializer if they don’t define any of their own custom initializers.
構造体タイプは、独自のカスタムイニシャライザを定義しない場合、自動的にメンバーワイズイニシャライザを受け取ります。

つまり以下の解釈で問題なさそうです。

  • custom initializer: 手動で書くinit
  • memberwise initializer: structにcustom initializerがない場合に自動生成されるinit

本題

環境

  • Xcode:14.1 (14B47b)
  • Swift:5.7.1
  • Swift Playground

問題

いきなりですが問題です。

第1問

以下のコードの出力はどうなるでしょうか?
CountA のinitは何回実行されるでしょうか?)

final class CountA {
    static var count = 0

    public init() {
        Self.count += 1
    }
}

struct A {
    var count: CountA = CountA()

    // custom init
    init(count: CountA) {
        self.count = count
    }
}

let a = A(count: CountA())
print(CountA.count) // ?

答えは 2 です。
デフォルト値の初期化はinitとは別に行われるため、var count: CountA = CountA()let a = A(count: CountA())で2回の処理が行われ、 CountA のinitは2回実行されます。

第2問

以下のコードの出力はどうなるでしょうか?
CountB のinitは何回実行されるでしょうか?)

final class CountB {
    static var count = 0

    public init() {
        Self.count += 1
    }
}

struct B {
    var count: CountB = CountB()

    // memberwise init
}

let b = B(count: CountB())
print(CountB.count) // ?

答えは 1 です。

なぜこうなるの?

調べた限りだとSwift.orgにこの仕様に関しては詳しく書いてありませんでした。
コンパイル後の出力を見れば何かわかる気がしますが、そこまでの知識がないのでわかりません。有識者の方、コメントよろしくお願いします。

なぜこうなるの・考察

custom init版でCountAが2回initされることに関しては、そこまで不自然ではない(理解できる挙動)ですね。

struct A {
    var count: CountA = CountA() // ここでもCountA生成

    // custom init
    init(count: CountA) {
        self.count = count
    }
}

let a = A(count: CountA()) // ここでもCountA生成

「デフォルト値の初期化・代入はinitとは別に行われる」 ということを意味しています。
ところが、memberwise initだと1回しか初期化が起こりません。つまり以下のようになっています。

struct B {
    var count: CountB = CountB() // <- ここでCountB生成は起きない

    // memberwise init
}

let b = B(count: CountB()) // <- ここでCountB生成は起こる(当たり前)

これは、「memberwise initはデフォルト値の生成プロセスに影響・関与する」ということになります。
memberwise initは単なる「自動で生成してくれるinit」ではなく 、「特殊なinit」という認識の方が正しそうです。

C++やJavaでは、「イニシャライザ=変数を初期化・定義するフェーズ」「コンストラクタ=クラスのインスタンスを作成するフェーズ」が分かれていたりします。その概念をSwiftにも当てはめると、

  • memberwise initはイニシャライザ(寄り)
  • custom initはコンストラクタ(寄り)

という感じになるのかもしれません。

それで...?

はい、ただの豆知識です。
紹介しといて何ですが、memberwise initによるデフォルト値生成削減の挙動はコスト的には良い物ですが、直感的ではないため、 狙って利用するべきではない と思います。structをpublicにしたら強制的にmemberwise init使えなくなリますし...

custom initで2回初期化が走る挙動をご存知の方はもうやられているかと思いますが、「デフォルト値を設定しているが、initでの代入を受け付けたい」というケースは、デフォルト引数(もしくはinitのオーバーロード)で吸収できるので、これが一番丸いと思います。

struct A {
    var count: CountA

    // custom init
    init(count: CountA = CountA()) {
        self.count = count
    }
}

let a = A(count: CountA())

余談

Xcodeの補完で"generate memberwise initializer"ってありますが、上記問題を考えても、swift.orgの定義を考えても、あれは"memberwise initializerと同じ定義を持つcustom initializer"であって、"memberwise initializer"じゃない気がしています、変えた方がいいような...

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?