#include <iostream>
int main()
{
const int n = 0;
volatile int m;
const volatile int o = 3;
m = 0; // ok, volatile修飾により、この代入は最適化でも削除されない
m = 1; // ok, 上記同様
// n = 1; // ng, const変数に再代入することは出来ない!
std::cout
<< "n : " << n
<< "m : " << m
<< "o : " << o;
}
void void_f()
{
const int n = 0; // ok
int m = n; // ok, ただ単に値をコピーしているだけ
m = 10; // ok, 変数mは普通のintの整数なので書き換えられる
}
// この4つ、全部同じ関数の宣言。オーバーロードもしてない。
// 引数のポインタでも参照でも無い型へのcv修飾は全て完全に無視される
int f(int);
int f(const int);
int f(volatile int);
int f(const volatile int);
int f(const int a)
{
//++a; // ng, const指定されているので、値は変更できない。ポインタでも参照でもない仮引数のconstは、
// オーバーロードには関係しないが、仮引数を関数の本体で使うときにはcv修飾子が適用されている
return a;
}
int g() { return 0; }
//const int g() { return 0; } // ng, ポインタでも参照でもない返り型のcv修飾は
// 仮引数の場合と違い、返り型だけが違う別の関数と認識される。
// しかし、返り型だけが違う関数はオーバーロード出来ないのでエラー
// ただし、呼び出し元で返り値を使用する時にはcv修飾は無視される
const int
などですが、int const
などとする流派もあります。これは宗教のようなものなので、好きな書き方で書きましょう。
volatile修飾子は、指定した変数のメモリアクセスに対して以下の2点を指定します。
olatile指定した変数への1度のメモリアクセスは、正確に1度として実行される、また、volatile指定した変数同士のメモリアクセスはコード上と対応すること。主に、ハードウェアなどのプログラム外部環境との通信するための変数に指定します。正直、volatile修飾子は通常のプログラミングでは使用しないので、覚えなくてもいいです。
普通はしませんが、cv修飾子はconst_cast演算子で付け外しをすることが出来ます。static_cast同様に、<>
に型名、()
の中にキャストしたい値を入れます。ただし、型名の所には、この後解説する参照やポインタを用いた型でないといけません。
const_cast
<
型名>
(
値)
n
は、const修飾しているので初期化以外では値を変更できません。ただし、値を変更しない動作である読み取りは出来るので、出力が出来ます。
変数o
は、const修飾とvolatile修飾しているので、どちらの特性も持ち合わせます。初期化以外で値は変更できないうえ、メモリアクセスはコードの通りに行われます。ただ、そもそもvolatile修飾子を普通のプログラミングでは見かけないので、
今後も出てくることはほとんどないでしょう…
g
は、返り値の型がポインタでも参照でもなく、cv修飾子が違う場合についてです。このような時は、関数の返り型
としては、違う型だと認識されるのでした。今回は、どちらも引数を取っていないので、全く同じ引数リストになっています。ということで、オーバーロードすることは出来ないので、どちらかを消さないとエラーです。
#include <iostream>
#include <string>
#include <chrono>
int &assign_zero(int &num)
{
num = 0;
return num;
}
int &dangerous()
{
int n = 0;
return n;
}
void section1()
{
std::cout << "section1\n";
int n = 1;
std::cout << n << "\n";
assign_zero(n);
std::cout << n << "\n";
assign_zero(n) = 5;
std::cout << n << "\n";
// 危ない!!
dangerous();
std::cout << "\n";
}
void copy(std::string str) {}
void ref(std::string &str) {}
void section2()
{
std::cout << "section2\n";
// ながーい文字列
std::string s(1ull << 30, '1');
// 実引数をコピーして仮引数を初期化
auto start = std::chrono::system_clock::now();
copy(s);
auto end = std::chrono::system_clock::now();
std::cout << "copy : " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms\n";
// 実引数を参照することで、実引数をコピーしない
start = std::chrono::system_clock::now();
ref(s);
end = std::chrono::system_clock::now();
std::cout << "ref : " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms\n";
std::cout << "\n";
}
int ret_zero() { return 0; }
int &ret_as_get(int &n) { return n; }
void lvalue_reference(int &) {}
void const_lvalue_reference(const int &) {}
void rvalue_reference(int &&) {}
void section3()
{
std::cout << "section3\n";
int n; // lvlaue
"string"; // lvalue
ret_as_get(n); // 返り型がlvalue referenceなので、呼び出し結果はlvalue
0; // rvalue
ret_zero(); // 返り型がnon referenceなので、呼び出し結果はrvalue
lvalue_reference(n);
// lvalue_reference(0); // ng, lvalue referenceにはlvalueしか束縛できない
const_lvalue_reference(n);
const_lvalue_reference(0); // ok, const lvalue referenceは何でも束縛できる
// rvalue_reference(n); // ng, rvalue referenceにはrvalueしか束縛できない
rvalue_reference(0);
std::cout << "\n";
}
int main()
{
section1();
section2();
section3();
}
section1
1
0
5
section2
copy : 457ms
ref : 0ms
section3
int a, &r = a;
とある時に、r = 0;
とすると正確にa = 0;
と同じになります。
#include <iostream>
/* section 1 ***********************************************************************************************/
int rvalue() { return 0; }
const int rvalue_() { return 0; }
const int &&crvalue() { return 0; }
void section1()
{
int lvalue = 0; // lvalue (変数はlvalue)
const int clvalue = 0; // const lvalue (const付き)
int rvalue(); // rvalue (返り値が参照でない関数の返り値はrvalue)
const int rvalue_(); // rvalue (const rvalueかと思いきや、ただのrvalue。つまり返り値intと同じ)
const int &&crvalue(); // const rvalue (これはrvalue referenceへのconstなので、const rvalue)
int value0 = lvalue; // ok
int value1 = clvalue; // ok
int value2 = rvalue(); // ok
int value3 = rvalue_(); // ok
int value4 = crvalue(); // ok
int &l_ref0 = lvalue; // ok
//int &l_ref1 = clvalue; // ng, 1.(cv指定子が消える方へは束縛できない)
//int &l_ref2 = rvalue(); // ng, 2.(lvalue referenceにrvalueは束縛できない)
//int &l_ref3 = rvalue_(); // ng, 2.
//int &l_ref4 = crvalue(); // ng, 1. + 2.
const int &cl_ref0 = lvalue; // ok
const int &cl_ref1 = clvalue; // ok
const int &cl_ref2 = rvalue(); // ok
const int &cl_ref3 = rvalue_(); // ok
const int &cl_ref4 = crvalue(); // ok
//int &&r_ref0 = lvalue; // ng, 1.(rvalue referenceにlvalueは束縛できない)
//int &&r_ref1 = clvalue; // ng, 1. + 2.(cv指定子が消える方へは束縛できない)
int &&r_ref2 = rvalue(); // ok
int &&r_ref3 = rvalue_(); // ok
//int &&r_ref4 = crvalue(); // ng, 2.
//const int &&cr_ref0 = lvalue; // ng, 1.(const rvalue referenceにlvalueは束縛できない)
//const int &&cr_ref1 = clvalue; // ng, 1.
const int &&cr_ref2 = rvalue(); // ok
const int &&cr_ref3 = rvalue_(); // ok
const int &&cr_ref4 = crvalue(); // ok
}
/**********************************************************************************************************/
/* section 2 **********************************************************************************************/
void show_n_lref(int n, int m)
{
std::cout << "n : " << n << "\tlref : " << m << "\n";
}
void show_rref(int n)
{
std::cout << "rref : " << n << "\n";
}
void section2()
{
int n = 1, &lref = n;
show_n_lref(n, lref);
n = 2;
show_n_lref(n, lref);
lref = 3;
show_n_lref(n, lref);
std::cout << "\n";
int &&rref = 42;
show_rref(rref);
rref = 334;
show_rref(rref);
std::cout << "\n";
}
/**********************************************************************************************************/
int main()
{
section1();
section2();
}
n : 1 lref : 1
n : 2 lref : 2
n : 3 lref : 3
rref : 42
rref : 334
section1
section1
では、ここまでで説明した事が示されています。section2
section2
で、変数rref
は初め42
でしたが、その後334
になるのは変だと思って42
には代入出来ないのだから、参照先が変更されている?」となるかもしれません。section2
section1
は規則の確認でしたが、関数section2
では参照の効果の確認です。lref
は変数n
を束縛しているので、lref
への操作はn
への操作になります。rref
については、先述した通りなので、割愛します。
volatile
を非推奨化 - cpprefjp C++日本語リファレンス