staticと翻訳単位と乱数生成器

明日はもう研究会だというのにC++ではまってなかなか進まないorz(ダメ院生)。

staticな変数とかはメモリの上で1つしか存在しないんだと思っていたら、そういうわけではなくソースファイルの中で、ということらしい(キーワード: 翻訳単位)。ちょっと長いが、下の例ではrandomという関数がstaticで定義されていて、グローバルに一つ乱数生成器があるかのように思えるが、bad1とbad2ではそれぞれ乱数生成器が作られてしまっていることが分かる(一列目と三列目の値が一緒)。

/Users/yasuhisa/cpp% g++ -c bad1.cpp; g++ -c bad2.cpp; g++ bad1.o bad2.o bad_main.cpp; ./a.out
0.945805, 0.712418, 0.973565, 0.237499, 0.475996, 0.512646, 0.0423286, 0.418472, 0.975018, 0.120168, 
0.573828, 0.0147206, 0.411715, 0.162769, 0.351549, 0.315692, 0.290796, 0.181917, 0.484411, 0.405733, 
0.945805, 0.712418, 0.973565, 0.237499, 0.475996, 0.512646, 0.0423286, 0.418472, 0.975018, 0.120168, 
bad_rand.hpp
#ifndef BAD_RAND_HPP
#define BAD_RAND_HPP

#include <boost/random.hpp>

namespace math {
  static boost::mt19937 gen_(static_cast<unsigned long>(time(NULL)));
  static boost::uniform_real<> dst_(0, 1);
  static boost::variate_generator<boost::mt19937, boost::uniform_real<> > rand_(gen_, dst_);
  static boost::uniform_real<>::result_type random() {
	return rand_();
  };
};
#endif
bad1.hpp
#ifndef BAD1_HPP
#define BAD1_HPP

#include <vector>
#include "bad_rand.hpp"

void rand_vector1(std::vector<double>& v);

#endif
bad1.cpp
#include "bad1.hpp"

void rand_vector1(std::vector<double>& v) {
  for (int i = 0; i < 10; i++) {
	v.push_back(math::random());
  }
};
bad2.hpp
#ifndef BAD2_HPP
#define BAD2_HPP

#include <vector>
#include "bad_rand.hpp"

void rand_vector2(std::vector<double>& v);

#endif
bad2.cpp
#include "bad2.hpp"

void rand_vector2(std::vector<double>& v) {
  for (int i = 0; i < 10; i++) {
	v.push_back(math::random());
  }
};
bad_main.cpp
#include <iostream>
#include <boost/foreach.hpp>

#include "bad1.hpp"
#include "bad2.hpp"

int main(int argc, char *argv[]) {

  std::vector<double> v;

  rand_vector1(v);
  BOOST_FOREACH(const double d, v) {
	std::cout << d << ", ";
  }
  std::cout << std::endl;

  v.clear();

  rand_vector1(v);
  BOOST_FOREACH(const double d, v) {
	std::cout << d << ", ";
  }
  std::cout << std::endl;

  v.clear();

  rand_vector2(v);
  BOOST_FOREACH(const double d, v) {
	std::cout << d << ", ";
  }
  std::cout << std::endl;
  v.clear();


  return 0;
};

解決方法

bad_rand.cppとかいうソースファイルを作ってやればよい。また、staticではなく無名なnamespaceを使うほうが推奨とのこと。

以上のことを書き変えると意図した通りに動いてくれることが確認できる。

bad_rand.hpp
#ifndef BAD_RAND_HPP
#define BAD_RAND_HPP

#include <boost/random.hpp>

namespace math {
  boost::uniform_real<>::result_type random();
};

#endif
bad_rand.cpp
#include "bad_rand.hpp"

namespace math {
  namespace {
	boost::mt19937 gen_(static_cast<unsigned long>(time(NULL)));
	boost::uniform_real<> dst_(0, 1);
	boost::variate_generator<boost::mt19937, boost::uniform_real<> > rand_(gen_, dst_);
  }
  boost::uniform_real<>::result_type random() {
	return rand_();
  };
};

こうするとグローバルに一つ乱数生成器ができたことが分かる。

/Users/yasuhisa/cpp% g++ -c bad_rand.cpp; g++ -c bad1.cpp; g++ -c bad2.cpp; g++ bad_rand.o bad1.o bad2.o bad_main.cpp; ./a.out 
0.926492, 0.660651, 0.130457, 0.352934, 0.959027, 0.528594, 0.374438, 0.477017, 0.520979, 0.739577, 
0.894449, 0.615949, 0.149972, 0.758141, 0.82672, 0.063639, 0.478917, 0.251075, 0.138039, 0.303442, 
0.55342, 0.731289, 0.396713, 0.158482, 0.201625, 0.596329, 0.528284, 0.260341, 0.0301604, 0.200705, 

あと、マルチスレッドとかを使うときには乱数生成器は状態を持っているので競合しては困る。ということでmutexのことを考えないといけない、というメモ。