1. ホーム
  2. c++

[解決済み] operator<< への未定義の参照

2022-02-20 04:42:04

質問

通常のクラス(テンプレートではありません)に、プライベートフレンドオペレータ<<.があります。

という宣言があります。

std::ostream& operator<<(std::ostream& out, const Position& position);

は、cppファイルで定義されています。

std::ostream& operator<<(std::ostream& out, const Position& position)
{
  out << position.getXPoint() << "," << position.getYPoint();
  return out;
}

がコンパイルされ、それを使用するmain関数にリンクされているのですが、それを使用すると未定義の参照が発生します。

しかし、この定義をメイン cpp ファイルの先頭に追加し、フレンド宣言を削除すると、正常に動作するようになりました...

私のメイン関数でどのようにそれを使用しているかは、次のとおりです。

std::cout << node->getPosition() << std::endl;

これ以上でも以下でもない...

リンカーエラーが表示されます。

<ブロッククオート

/home/luke/Desktop/pathfinder/parse_world.cpp:34: undefined reference to `pathfinder::operator<<(std::ostream&, pathfinder::Position const&)'未定義参照

そして、これがクラスヘッダです...

#ifndef PATHFINDER_H
#define PATHFINDER_H
#include <ostream>
#include <istream>
#include <list>
#include <vector>
#include <stdexcept>
#include <cstring>
namespace pathfinder
{
class Node;
typedef unsigned int GCost;
typedef std::vector<std::vector<Node*> > World;
typedef std::vector<Node*> WorldRow;
class Position
{
public:
    typedef unsigned int point_type;
private:
    point_type* xPoint_;
    point_type* yPoint_;
    friend std::ostream& operator<<(std::ostream& out, const Position& position);
public:
    Position(const point_type& xPoint = 0, const point_type& yPoint = 0);
    Position(const Position& position);
    Position(Position&& position);
    ~Position();

    Position& operator=(const Position& position);
    Position& operator=(Position&& position);

    point_type getXPoint() const;
    point_type getYPoint() const;

    void setXPoint(const point_type& xPoint);
    void setYPoint(const point_type& yPoint);
};
class Node
{
private:
    bool* isPassable_;
    bool* isStartingNode_;
    bool* isTargetNode_;
    Position* position_;
    GCost* additionalGCost_;
    Node* parent_;
public:
    Node(const bool& isPassable = true, const bool& isStartingNode = false, const bool& isTargetNode = false, const Position& position = Position(0,0), const GCost& additionalGCost = 0, Node* parent = nullptr);
    Node(const Node& node);
    Node(Node&& node);
    ~Node();

    Node& operator=(const Node& node);
    Node& operator=(Node&& node);

    bool isPassable() const;
    bool isStartingNode() const;
    bool isTargetNode() const;
    Position getPosition() const;
    GCost getAdditionalGCost() const;
    Node* getParent() const;

    void setAsPassable(const bool& isPassable);
    void setAsStartingNode(const bool& isStartingNode);
    void setAsTargetNode(const bool& isTargetNode);
    void setPosition(const Position& position);
    void setAdditionalGCost(const GCost& additionalGCost);
    void setParent(Node* parent);
};
class WorldHelper
{
public:
    static World fromStream(std::istream& input);
    static void syncPositions(World& world);
    static void uninitializeWorld(World& world);
    static Node* findStartingNode(const World& world);
    static Node* findTargetNode(const World& world);
};
class Pathfinder
{
public:
    virtual std::list<Node*> operator()(World& world) const = 0;
};
};
#endif //PATHFINDER_H

friend宣言を削除したら、次のようなエラーメッセージが表示されるようになりました。

std::ostream {aka std::basic_ostream}' lvalue を 'std::basic_ostream&&' にバインドできない。

std::coutステートメントと同じ行で発生しています...

さて、どうしたものか...。

よろしくお願いします

どのように解決するのですか?

記述から推測すると、2つの operator<< が、1つだけ定義されています。例えば

namespace A {
   struct B {
      friend std::ostream& operator<<( std::ostream&, B const & ); // [1]
   };
}
std::ostream& operator<<( std::ostream& o, A::B const & ) {        // [2]
   return o;
}

1]の行では、1つの A::operator<< という型にADLを通して見つけることができる関数です。 B が、[2]では ::operator<< . コンパイラがこのコードを見たとき

A::B b;
std::cout << b;

ADLを使用し A::operator<< (friend宣言から)それを使うが、その関数は未定義である。もし friend 宣言のインスタンスが1つだけ存在します。 operator<< グローバル名前空間で宣言され、定義されたものは、通常のルックアップで見つかります。

また、プログラム中に using ディレクティブがある場合、[2] の定義が明示的に A の名前空間が引数になります。これは、次のような説明にもなります。 定義 は、その型の残りのメンバーである。

// cpp [assume that the definition of B contained a member f
using namespace A;
void B::f() {
}

の定義は B::f は (コード上) グローバルな名前空間に存在しますが、using ディレクティブがあるためです。 BA の名前空間と同じで、型指定子は A::B と等価な定義になるのは void ::A::B::f() {} を解決した後 B . これは ではない は、フリー関数で発生します。

ディレクティブは今回のような微妙なエラーを許してしまうので、使用しないことをお勧めします。また、実際には 定義 演算子を明示的に名前空間内で使用する必要があります。 宣言 を名前空間内で使用します。

namespace A {
   struct B  { friend std::ostream& operator<<( std::ostream&, B const & ); };
   std::ostream& operator<<( std::ostream&, B const & );
}
std::ostream& A::operator<<( std::ostream& o, B const & ) {
   return o;
}

このトリック(完全修飾して自由関数を本来の名前空間の外で定義すること)は、関数の定義が暗黙のうちに宣言されるのを避けるために使われることがあり、この種のエラーが発生しやすくなっています。例えば、適切な名前空間で演算子を定義したけれども、シグネチャが少し違っていた場合。

namespace A {
   struct B  {
      friend std::ostream& operator<<( std::ostream&, B const & ); // [1]
   };
   std::ostream& operator<<( std::ostream&, B & ) {                // [3]
      return o;
   }
}

3]の定義も宣言ですが、[1]で宣言したものとは異なる関数を宣言しており、リンカーが[1]を見つけられないのはなぜかと頭を悩ませることになるかもしれません。