yoppa.org


immediate bitwave

Blog

C++の疑問: 値渡し、参照渡し、ポインタ渡し

※ 末尾に追記あり。

C++、いろいろ未だにわからにことだらけなのだけれど、関数に値を送る場合の

  • 値渡し
  • 参照渡し
  • ポインタ渡し

の違いがいまだにすっきりと整理できていない。

C++、自分の場合はoFで使うことがほとんどなので、oFのコードで実験してすっきりしてみようとシンプルなコードを書いてみた。

力をベクトルで与えると運動するParticleというクラスを以前から作っていたいので、これはそのまま流用。長くなるのでコードの内容は割愛。

Particleを操作するためのMoverというクラスをつくってみた。このMoverクラスは3種類の方法でParticleのインスタンスを受けとることができる

  • A: 値渡し
  • B: 参照渡し
  • C: ポインタ渡し

Mover.h

#pragma once
#include "ofMain.h"
#include "Particle.h"

class Mover{
public:
    void setParticle(Particle p);       // 値渡し
    void setParticleRef(Particle &p);   // 参照渡し
    void setParticlePtr(Particle *p);   // ポインタ渡し
    
    void update();
    
    Particle particleA;
    Particle particleB;
    Particle *particleC;
};

Mover.cpp

#include "Mover.h"

// A: オブジェクトの値渡し
void Mover::setParticle(Particle p){
    particleA = p;
    particleA.velocity = ofVec2f(10.0, 0);
}

// B: オブジェクトの参照渡し
void Mover::setParticleRef(Particle &p){
    particleB = p;
    particleB.velocity = ofVec2f(10.0, 0);
}

// C: オブジェクトのポインタ渡し
void Mover::setParticlePtr(Particle *p){
    particleC = p;
    particleC->velocity = ofVec2f(10.0, 0);
}

// それぞれのオブジェクトを更新
void Mover::update(){
    particleA.resetForce();
    particleA.update();

    particleB.resetForce();
    particleB.update();

    particleC->resetForce();
    particleC->update();
}

これを、ofAppから呼びだしてみる。

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(60);
    ofBackground(0);

    // パーティクルの初期位置を設定
    particleA.position = ofVec2f(10, 10);
    particleB.position = ofVec2f(10, 110);
    particleC.position = ofVec2f(10, 210);
    
    // A: オブジェクトの値渡し
    mover.setParticle(particleA);
    // B: オブジェクトの参照渡し
    mover.setParticleRef(particleB);
    // C: オブジェクトのポインタ渡し
    mover.setParticlePtr(&particleC);
}

//--------------------------------------------------------------
void ofApp::update(){
    mover.update();
}

//--------------------------------------------------------------
void ofApp::draw(){
    // A: 値渡しの描画
    ofSetColor(ofColor::red);
    particleA.draw();
    // B: 参照渡しの描画
    ofSetColor(ofColor::blue);
    particleB.draw();
    // C: ポインタ渡しの描画
    ofSetColor(ofColor::yellow);
    particleC.draw();
}

実行結果はどうなったかというと

  • A: 値渡し → 動かない
  • B: 参照渡し → 動かない
  • C: ポインタ渡し → 動いた

Aの値渡しが動かないのは、まあ予想内の挙動。そして、Cのポインタ渡しでは狙い通りofAppのParticleのインスタンスもMoverから更新され、無事動いた。

解せないのは、Bの参照渡しの挙動。参照渡しはオブジェクトの実体ではなく、あくまでその参照として渡っているので、ofApp側のインスタンスも連動して更新されると考えていたのだが、このコードではそうならなかった。うーん、何故なんでしょう。

C++はムズカシイネ…

プロジェクト全体は下記にあります。

【追記】: Facebookでコメントしていただき、参照渡しがうまくいかなかった原因判明。Mover.cppの11行目で、”particleB = p;”と代入している。ここでpをparticleBにコピーしてしまっていて、その結果参照から外れてしまったということのようだ。うーん、なるほど。参照渡しはクラスのメンバとして扱うのは難しいですね…

こう修正したら、参照渡しでも動きました。クラス変数を参照渡しするには、コンストラクタの初期値として渡す必要あるみたい。ううむ、扱い難しいですね。

Mover.h

#pragma once
#include "ofMain.h"
#include "Particle.h"

class Mover{
    
public:
    // 参照渡しは、コンストラクタのイニシャライザで
    Mover(Particle &particleB):particleB(particleB){};

    void setParticle(Particle p);       // 値渡し
    void setParticlePtr(Particle *p);   // ポインタ渡し
    
    void update();
    
    Particle particleA;
    Particle &particleB;
    Particle *particleC;
};

Mover.cpp

#include "Mover.h"

// A: オブジェクトの値渡し
void Mover::setParticle(Particle p){
    particleA = p;
    particleA.velocity = ofVec2f(10.0, 0);
}

// C: オブジェクトのポインタ渡し
void Mover::setParticlePtr(Particle *p){
    particleC = p;
    particleC->velocity = ofVec2f(10.0, 0);
}

// それぞれのオブジェクトを更新
void Mover::update(){
    particleA.resetForce();
    particleA.update();

    particleB.resetForce();
    particleB.update();

    particleC->resetForce();
    particleC->update();
}

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(60);
    ofBackground(0);

    // パーティクルの初期位置を設定
    particleA.position = ofVec2f(10, 10);
    particleB.position = ofVec2f(10, 110);
    particleB.velocity = ofVec2f(10, 0);
    particleC.position = ofVec2f(10, 210);
    
    // B:参照渡しは、コンストラクタで渡す
    mover = new Mover(particleB);
    
    // A: オブジェクトの値渡し
    mover->setParticle(particleA);
    
    // C: オブジェクトのポインタ渡し
    mover->setParticlePtr(&particleC);
}

//--------------------------------------------------------------
void ofApp::update(){
    mover->update();
}

//--------------------------------------------------------------
void ofApp::draw(){
    // A: 値渡しの描画
    ofSetColor(ofColor::red);
    particleA.draw();
    // B: 参照渡しの描画
    ofSetColor(ofColor::blue);
    particleB.draw();
    // C: ポインタ渡しの描画
    ofSetColor(ofColor::yellow);
    particleC.draw();
}