Edited at

c++に関する個人的疑問とそれに対応する答え

c++に関して湧いた疑問をメモしていく.


既に答えが分かった疑問


複数の関数を呼ぶ順番が決まっている場合にまとめるべきか呼び出し側に任せるか

Effective C++の23章にかかれていた.

メンバ関数としてまとめ関数を定義するのではなく,

そのクラスと同じnamespaceに,そのクラスのインスタンスを引数にとるまとめ関数を定義してあげるのが良い.

前者だとカプセル化を害しているので良くない.


unique_ptrのクラスを前方宣言したときにコンパイルで怒られる


Base.h

#ifndef BASE_H

#define BASE_H

#include <iostream>

class Base {
public:
Base() { std::cout << "Base constructor is called" << std::endl; };
virtual ~Base() {};
};

#endif



Hoge.h

#ifndef HOGE_H

#define HOGE_H

#include <memory>

class Base;
class Hoge {
public:
Hoge();
private:
std::unique_ptr<Base> pBase_;
};

#endif



Hoge.cpp

#include "Hoge.h"

#include "Base.h"

Hoge::Hoge() : pBase_(std::make_unique<Base>()) {}


test.cpp

#include "Hoge.h"


int main() {
std::unique_ptr<Hoge> phoge = std::make_unique<Hoge>();
}


CMakeLists.txt

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を以下のようにデストラクタをインラインで書いてみても


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で定義しておくと


Hoge.h

#ifndef HOGE_H

#define HOGE_H

#include <memory>

class Base;
class Hoge {
public:
Hoge();
~Hoge();
private:
std::unique_ptr<Base> pBase_;
};

#endif



Hoge.cpp

#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でないメンバ関数がどのメンバ変数を変更するのか把握しづらい.

そうしたときに,メンバ関数の引数に,その関数の内部で変更するメンバ変数の参照を渡してあげるようにすると,関数の呼び出しを見るだけで,どのメンバ変数を変更するかが分かる.

一方で,そうすると関数の引数が多くなってしまうというデメリットがある.

本来はメンバ変数なので引数で渡す必要はないはず.


  • 関数名でどの変数を変更しうるのかが分かるようにする

  • メンバ変数の種類が少なくなるようなクラス設計をする

というのが正解なのだろうか?


複数のクラスのインスタンスで共通のshared_ptrを共有すると,コードの可読性が下がらないだろうか?

次のようなコードがあったとする.

Car::driveを呼ぶ前後で,p_my_energyの指す値が変わる.

今回の例はソースが短いから気にならないが,もっと大きいクラスだった場合に,p_my_energyがいつどのタイミングで変わるのかが追いづらくなる気がする.

デザインパターン的にはどうするのが良いか?

Effective C++ の28項には,privateのメンバ変数への参照を返すのは良くないと書いてあるが,この件と関係あるかもしれない.


car.cpp

#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;
}