c++に関して湧いた疑問をメモしていく.
既に答えが分かった疑問
複数の関数を呼ぶ順番が決まっている場合にまとめるべきか呼び出し側に任せるか
Effective C++の23章にかかれていた.
メンバ関数としてまとめ関数を定義するのではなく,
そのクラスと同じnamespaceに,そのクラスのインスタンスを引数にとるまとめ関数を定義してあげるのが良い.
前者だとカプセル化を害しているので良くない.
unique_ptrのクラスを前方宣言したときにコンパイルで怒られる
#ifndef BASE_H
#define BASE_H
#include <iostream>
class Base {
public:
Base() { std::cout << "Base constructor is called" << std::endl; };
virtual ~Base() {};
};
#endif
#ifndef HOGE_H
#define HOGE_H
#include <memory>
class Base;
class Hoge {
public:
Hoge();
private:
std::unique_ptr<Base> pBase_;
};
#endif
#include "Hoge.h"
#include "Base.h"
Hoge::Hoge() : pBase_(std::make_unique<Base>()) {}
#include "Hoge.h"
int main() {
std::unique_ptr<Hoge> phoge = std::make_unique<Hoge>();
}
add_definitions(-std=c++14)
add_library(hoge SHARED Hoge.cpp)
add_executable(test test.cpp)
target_link_libraries(test hoge)
これではコンパイルが通らない.
[ 75%] Building CXX object CMakeFiles/test.dir/test.o
In file included from /usr/include/c++/5/memory:81:0,
from /tmp/b/Hoge.h:4,
from /tmp/b/test.cpp:1:
/usr/include/c++/5/bits/unique_ptr.h: In instantiation of ‘void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Base]’:
/usr/include/c++/5/bits/unique_ptr.h:236:17: required from ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Base; _Dp = std::default_delete<Base>]’
/tmp/b/Hoge.h:8:7: required from ‘void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Hoge]’
/usr/include/c++/5/bits/unique_ptr.h:236:17: required from ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Hoge; _Dp = std::default_delete<Hoge>]’
/tmp/b/test.cpp:4:56: required from here
/usr/include/c++/5/bits/unique_ptr.h:74:22: error: invalid application of ‘sizeof’ to incomplete type ‘Base’
static_assert(sizeof(_Tp)>0,
^
Hoge.h
を以下のようにデストラクタをインラインで書いてみても
#ifndef HOGE_H
#define HOGE_H
#include <memory>
class Base;
class Hoge {
public:
Hoge();
~Hoge() {};
private:
std::unique_ptr<Base> pBase_;
};
#endif
コンパイルは以前通らない.
[ 75%] Building CXX object CMakeFiles/test.dir/test.o
In file included from /usr/include/c++/5/memory:81:0,
from /tmp/b/Hoge.h:4,
from /tmp/b/test.cpp:1:
/usr/include/c++/5/bits/unique_ptr.h: In instantiation of ‘void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Base]’:
/usr/include/c++/5/bits/unique_ptr.h:236:17: required from ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Base; _Dp = std::default_delete<Base>]’
/tmp/b/Hoge.h:11:11: required from here
/usr/include/c++/5/bits/unique_ptr.h:74:22: error: invalid application of ‘sizeof’ to incomplete type ‘Base’
static_assert(sizeof(_Tp)>0,
デストラクタをHoge.h
では宣言しておいて,Hoge.cpp
で定義しておくと
#ifndef HOGE_H
#define HOGE_H
#include <memory>
class Base;
class Hoge {
public:
Hoge();
~Hoge();
private:
std::unique_ptr<Base> pBase_;
};
#endif
#include "Hoge.h"
#include "Base.h"
Hoge::Hoge() : pBase_(std::make_unique<Base>()) {}
Hoge::~Hoge() {}
コンパイルは通る.
https://www.slideshare.net/KeisukeFukuda/effective-modern-c-item-22 にまとまっている.
https://www.ruche-home.net/boyaki/2012-09-26/C11pimpl が分かりやすい.
自動生成されるデストラクタはインライン展開されるが,前方宣言しているとエラーが出るよねという話.
まだ答えが分かっていない疑問
メンバ関数の引数にメンバ変数を使うのはアリかナシか
ソースコードを読んでいて,メンバ変数が多いクラスの場合,そのクラスのあるconstでないメンバ関数がどのメンバ変数を変更するのか把握しづらい.
そうしたときに,メンバ関数の引数に,その関数の内部で変更するメンバ変数の参照を渡してあげるようにすると,関数の呼び出しを見るだけで,どのメンバ変数を変更するかが分かる.
一方で,そうすると関数の引数が多くなってしまうというデメリットがある.
本来はメンバ変数なので引数で渡す必要はないはず.
- 関数名でどの変数を変更しうるのかが分かるようにする
- メンバ変数の種類が少なくなるようなクラス設計をする
というのが正解なのだろうか?
- www.slideshare.net/slideshow/embed_code/key/bgbkoCfRlkIu2R?startSlide=55
- https://togetter.com/li/472338
複数のクラスのインスタンスで共通のshared_ptrを共有すると,コードの可読性が下がらないだろうか?
次のようなコードがあったとする.
Car::driveを呼ぶ前後で,p_my_energyの指す値が変わる.
今回の例はソースが短いから気にならないが,もっと大きいクラスだった場合に,p_my_energyがいつどのタイミングで変わるのかが追いづらくなる気がする.
デザインパターン的にはどうするのが良いか?
Effective C++ の28項には,privateのメンバ変数への参照を返すのは良くないと書いてあるが,この件と関係あるかもしれない.
#include <iostream>
#include <memory>
// g++ -std=c++11 hoge.cpp
class Energy {
public:
explicit Energy() : left_(100) {};
int left_; // ガソリンの残り容量[L]
};
class Engine {
public:
explicit Engine(const std::shared_ptr<Energy>& p_energy)
: efficiency_(10.0), p_energy_(p_energy) {};
double work(int energy) {
if (p_energy_->left_ < energy) {
std::cout << "run short of energy..." << std::endl;
return 0;
} else {
p_energy_->left_ -= energy;
return energy * efficiency_;
}
}
double efficiency_; // 燃費[km/L]
private:
std::shared_ptr<Energy> p_energy_;
};
class Car {
public:
explicit Car(const std::shared_ptr<Energy>& p_energy, const std::shared_ptr<Engine>& p_engine)
: position_(0), p_energy_(p_energy), p_engine_(p_engine) {};
void drive(double command) {
int energy_consumption = static_cast<int>(command / p_engine_->efficiency_);
double distance = p_engine_->work(energy_consumption);
std::cout << "travel: " << distance << "[km]" << std::endl;
position_ += distance;
};
void display(std::string suffix = "") const {
std::cout << suffix << "gasoline: " << p_energy_->left_ << "[L]" << std::endl;
};
double position_; // 進んだ位置[km]
private:
std::shared_ptr<Energy> p_energy_;
std::shared_ptr<Engine> p_engine_;
};
int main() {
std::shared_ptr<Energy> p_my_energy;
std::cout << "shared_ptr count: " << p_my_energy.use_count() << std::endl;
{
p_my_energy = std::make_shared<Energy>();
std::cout << "shared_ptr count: " << p_my_energy.use_count() << std::endl;
std::shared_ptr<Engine> p_my_engine = std::make_shared<Engine>(p_my_energy);
std::cout << "shared_ptr count: " << p_my_energy.use_count() << std::endl;
Car my_car(p_my_energy, p_my_engine);
std::cout << "shared_ptr count: " << p_my_energy.use_count() << std::endl;
my_car.display("No.1: ");
my_car.drive(800.0);
my_car.display("No.2: ");
my_car.drive(800.0);
my_car.display("No.3: ");
}
std::cout << "shared_ptr count: " << p_my_energy.use_count() << std::endl;
}