プログラミングメモ

自分の中で情報整理するために書いたC++とGo、Pythonに関するメモです。随時更新しています。

クラス

クラスを作成する

C++でクラスを作成する

#include <iostream>
#include <utility>

class MyClass {
public:
    std::string public_name;

    MyClass(std::string public_name, std::string private_name) {
        this->public_name = std::move(public_name);
        this->private_name = std::move(private_name);
        std::cout << "constructor called" << std::endl;
    }

    ~MyClass() {
        std::cout << "destructor called" << std::endl;
    }

    void show_name() {
        std::cout << "public_name: " << this->public_name << std::endl;
        std::cout << "private_name: " << this->private_name << std::endl;
    }

private:
    std::string private_name;
};


int
main() {
    MyClass myclass("yamada", "taro");
    myclass.show_name();

    return 0;
}

Goでクラスを作成する

Goにはそもそもクラスがない。しかし関数を構造体に紐付けてクラスのような振る舞いをさせることができる。Goは大文字小文字で公開・非公開範囲が決まり、その単位はpackage単位となる(以下の例ではmain package)。

package main

import "fmt"

type MyClass struct {
	Public_name  string
	Private_name string // プライベートではない
}

func (p *MyClass) print_name() {
	fmt.Println(p.Public_name)
	fmt.Println(p.Private_name)
}

func main() {
	myclass := MyClass{"yamada", "taro"}
	myclass.print_name()
}

Pythonでクラスを作成する

Pythonのクラスにはpublicprivateはないが、メンバ変数の名前の先頭に__(アンダースコアを2つ)を付けるとprivateのような振る舞いをする。コンストラクタは__init__(self, 引数, …)となり、デコンストラクタは__del__(self)になる。

class MyClass:
    def __init__(self, public_name, secret_name):
        self.public_name = public_name
        self.__secret_name = secret_name  # privateにする

    def __del__(self):
        print("destructor called")

    def print_name(self):
        print(self.public_name)
        print(self.__secret_name)


if __name__ == '__main__':
    MyClass = MyClass("yamada", "taro")
    # print(MyClass.__secret_name) ※privateのような振る舞いをするのでアクセスできない

    MyClass.print_name()

クラスの継承

C++の継承

C++は基本クラス(BaseClass)のコンストラクタとデコンストラクタが派生クラス(NewClass)で自動的に実行される。派生クラスはpublicprotectedにアクセスできるがprivate_valにはアクセスできない。

#include <iostream>

class BaseClass {
public:
    BaseClass() {
        std::cout << "BaseClass constructor" << std::endl;
    }

    ~BaseClass() {
        std::cout << "BaseClass destructor" << std::endl;
    }

private:
    int private_val{};
protected:
    int protected_val{};

};


class NewClass : public BaseClass {
public:
    NewClass() {
        std::cout << "NewClass constructor" << std::endl;
    }

    ~NewClass() {
        std::cout << "NewClass destructor" << std::endl;
    }

    void show_val();
};


void NewClass::show_val() {
    std::cout << "protected_val: " << protected_val << std::endl;
//    std::cout << "private_val: " << private_val << std::endl; // これはアクセス不可
}


int main() {
    NewClass newClass;
    newClass.show_val();
    return 0;
}

Goの継承

Goにはクラスがないため継承もない。ただし、構造体の中に別の構造体を定義することで継承のような振る舞いをさせることができる。以下のコードは基本クラスをBaseClass、派生クラスをNewClassとして模倣した例です。オーバーライドのようなこともできて、h.hello()の実行結果はnew helloが表示されます。

package main

import "fmt"

type BaseClass struct {
}

func (b *BaseClass) hello() {
	fmt.Println("hello")
}

type MewClass struct {
	BaseClass
}

func (n *MewClass) hello() {
	fmt.Println("new hello")
}
func (n *MewClass) hi() {
	fmt.Println("hi")
}
func main() {
	var n MewClass
	n.hello() // new hello
	n.hi()
}

Pythonの継承

Pythonは継承元のコンストラクタとデコンストラクタを明示的に呼ばないと実行されない。

class BaseClass:
    def __init__(self):
        print("BaseClass constructor")

    def __del__(self):
        print("BaseClass destructor")


class SubClass(BaseClass):
    def __init__(self):
        super().__init__()
        print("SubClass constructor")

    def __del__(self):
        super().__del__()
        print("SubClass destructor")


if __name__ == '__main__':
    sub = SubClass()
    del sub

オーバーライド

C++のオーバーライド

派生クラスでメソッドを上書きする場合、基本クラスでvirtualを付ける。virtualを付けた関数は仮想関数となる。

#include <iostream>

class BaseClass {
public:
    void func1() {
        std::cout << "base func1" << std::endl;
    }

    virtual void func2() {
        std::cout << "base func2" << std::endl;
    }
};


class NewClass : public BaseClass {
public:
    void func1() {
        std::cout << "new func1" << std::endl;
    }

    void func2() override {
        std::cout << "new func2" << std::endl;
    }
};

int main() {
    NewClass newClass;
    newClass.func1(); // new func1
    newClass.func2(); // new func2

    BaseClass *p;
    p = new NewClass;
    p->func1(); // base func1
    p->func2(); // new func1

    return 0;
}

基本クラスに仮想関数がある場合、基本クラスのデコンストラクタをvirtualで定義する必要がある(と教えられる)。なぜならば派生クラスを基本クラスのポインタとして利用する場合、派生クラスのデコンストラクタが実行されないから。たとえば次のコード。

#include <iostream>

class BaseClass {
public:
    BaseClass() {
        std::cout << "BaseClass constructor" << std::endl;
    }

    ~BaseClass() {
        std::cout << "BaseClass destructor" << std::endl;
    }

    virtual void func1() = 0;
};


class NewClass : public BaseClass {
public:
    NewClass() {
        std::cout << "NewClass constructor" << std::endl;
    }

    ~NewClass() {
        std::cout << "NewClass destructor" << std::endl;
    }

    void func1() override {
        std::cout << "new func1" << std::endl;
    }
};

int main() {
    BaseClass *baseClass = new NewClass();
    baseClass->func1();
    delete baseClass; // NewClassのデコンストラクタが実行されない

    return 0;
}

コンパイルして実行すると次のように表示される。NewClassのデコンストラクタが実行されていない。

BaseClass constructor
NewClass constructor
new func1
BaseClass destructor

次にBaseClassのデコンストラクタにvirtualを付ける。

#include <iostream>

class BaseClass {
public:
    BaseClass() {
        std::cout << "BaseClass constructor" << std::endl;
    }

    virtual ~BaseClass() {
        std::cout << "BaseClass destructor" << std::endl;
    }

    virtual void func1() = 0;
};


class NewClass : public BaseClass {
public:
    NewClass() {
        std::cout << "NewClass constructor" << std::endl;
    }

    ~NewClass() override {
        std::cout << "NewClass destructor" << std::endl;
    }

    void func1() override {
        std::cout << "new func1" << std::endl;
    }
};

int main() {
    BaseClass *baseClass = new NewClass();
    baseClass->func1();
    delete baseClass;

    return 0;
}

コンパイルして実行すると今度はNewClassのデコンストラクタが実行されている。

BaseClass constructor
NewClass constructor
new func1
NewClass destructor
BaseClass destructor

派生クラスでリソースを確保している場合は開放されないままプログラムが継続されることになり、メモリリークなどの問題が発生します。このような事態を避けるためにも、仮想関数をメンバーに持つクラスはデコンストラクタにvirtualを付ける必要があります。

抽象クラス

C++の抽象クラス

virtual void func1() = 0;のように記述すると実装を伴わない純粋仮想関数となり、このクラスのインスタンスを生成できなくなる。

#include <iostream>

class BaseClass {
public:
    virtual void func1() = 0;
};


class NewClass : public BaseClass {
public:
    void func1() override {
        std::cout << "new func1" << std::endl;
    }
};

int main() {
    NewClass newClass;
    newClass.func1();

    // BaseClass baseClass; ※抽象クラスはインスタンスを生成できない

    return 0;
}

インターフェース

インターフェースはクラスやオブジェクトがどのようなメソッドを持つべきなのか定義するもので、実装を伴いません。

Javaのインターフェース

Javaには言語仕様としてインターフェースが用意されていて、interfaceで定義しクラスではimplementsを使用して実装する。

interface Animal {
    void eat();
    void sleep();
}

class Dog implements Animal {
    public void eat() {
        System.out.println("Dog eats");
    }

    public void sleep() {
        System.out.println("Dog sleeps");
    }
}

C++のインターフェース

C++にはインターフェースはないけれど、やることは抽象クラスそのものです。

#include <iostream>

class Animal {
public:
    virtual void eat() = 0;
    virtual void sleep() = 0;
};

class Dog : public Animal {
public:
    void eat() override {
        std::cout << "Dog eats" << std::endl;
    }

    void sleep() override {
        std::cout << "Dog sleeps" << std::endl;
    }
};

int main() {
    Dog dog;
    dog.eat();
    dog.sleep();
    return 0;
}

Goのインターフェース

Goではtypeとしてinterfaceを定義できます。interfaceとして定義されたすべてのメソッドを実装した場合、自動的にインターフェースを実装したとみなされます。

package main

import "fmt"

type Animal interface {
	Eat()
	Sleep()
}

type Dog struct {
	name string
}

func (d Dog) Eat() {
	fmt.Println("Dog eats")
}

func (d Dog) Sleep() {
	fmt.Println("Dog sleeps")
}

func (d Dog) ShowName() {
	fmt.Println("Dog's name is ", d.name)
}

func EatAndSleep(a Animal) {
	a.Eat()
	a.Sleep()
	//a.ShowName() ※これはインターフェースに含まれないのでエラー
}
func main() {
	d := Dog{name: "Taro"}
	d.ShowName()
	EatAndSleep(d)
}

Pythonのインターフェース

Pythonにはインターフェースはありませんが、ABCという抽象基底クラスを使ってインタフェースのような振る舞いをさせることができます。

from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def eat(self):
        pass

    @abstractmethod
    def sleep(self):
        pass


class Dog(Animal):
    def eat(self):
        print("Dog eats")

    def sleep(self):
        print("Dog sleeps")


if __name__ == '__main__':
    dog = Dog()
    dog.eat()
    dog.sleep()