Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > unresolved external symbol в *.obj при связывании


Автор: KasMP 16.7.2009, 11:42
Цитата(Ошибка @  VS 2008 Express Edition)

1>Try.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall Figure::Draw(struct HWND__ *)" (?Draw@Figure@@UAEXPAUHWND__@@@Z)
1>...\Try.exe : fatal error LNK1120: 1 unresolved externals


В программе нет ничего особенного: используется самое элементарное из WinAPI, есть *.h с базовым классом (и виртуальными функциями), еще *.h с его наследниками и реализациями этих виртуальных функций, несколько *.cpp  с несложными функциями. Нигде ничего особенного не задействуется!
Основное содержание файлов, с которыми все это может быть связано:
  • Try.cpp:
    обычный файл Win32-проекта, содержащий
    Код

    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow);
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    ...

    Ну и его #include:
    Код

    #include "stdafx.h"
    #include "Try.h"

    #include "Draw.h"
    #include "global.h"

    #include "cl_Figure.h"
    #include "cl_Point.h"

    #include <fstream>
    using namespace std;

    Ничего серьезного в нем сейчас нет.
  • global.h
    Код

    short    scale = 60,            // нечто типа масштаба всего рисунка
            stroke = scale / 10;
    short    x_0 = 1,            // расстояние между границами клиентской области
            y_0 = 1;            // и координатной плоскостью в условных единицах
    int        x0 = scale * x_0,
            y0 = scale * y_0;
  • cl_Figure.h
    Код

    #include <fstream>
    using namespace std;

    enum        TypeOfFigure {nothing, point, line, ellipse, rectangle};
    const short    NumberOfTypes = rectangle;

    struct color {short r, g, b;};

    class Figure {
    protected:
        char    type;
        color    fpen;
    public:
        bool ValidType (char i) {
            if ((i > NumberOfTypes+'0') || (i < '0')) return false;
            return true;
        };
        Figure (ifstream *f, char ttype) {
            //*f >> fpen.r >> fpen.g >> fpen.b;
            *f >> fpen.r;
            *f >> fpen.g;
            *f >> fpen.b;
            type = ttype;};
        Figure () {type = '0'; fpen.r = fpen.g = fpen.b = 0;};
        Figure (char i) {
            if (ValidType(i)) type = i; else type = '0';
            fpen.r = fpen.g = fpen.b = 0;
        };
        Figure (char i, color j) {
            if (ValidType(i)) type = i; else type = '0';
            fpen = j;
        };
        char GetType () {return type;};
        bool SetType (char i) {
            fpen.r = fpen.g = fpen.b = 0;
            if (! ValidType(i) ) {type = 0; return false;}
            type = i;
            return true;
        };
        void SetPen (color i) {fpen = i;};
        color GetPen () {return fpen;};
        virtual void Draw (HWND hWnd);
        //virtual bool PtInFigure (POINT pt);
    };
  • cl_Point.h
    Код

    class Point : public Figure {
    protected:
        POINT    p;
    public:
        Point (ifstream *f): Figure(f,'1') {
            //*f >> p.x >> p.y;};
            *f >> p.x;
            *f >> p.y;
        };
        Point () : Figure('1') {p.x = -1; p.y = -1;};
        Point (color ppen): Figure('1',ppen) {p.x = -1; p.y = -1;};
        Point (color ppen, POINT pp): Figure('1',ppen) {p = pp;};
        void SetPoint (POINT pp) {p = pp;}; void SetX (int x) {p.x = x;}; void SetY (int y) {p.y = y;};
        void Draw (HWND hWnd);
        bool PtInFigure (POINT pt);
    };

    void Point :: Draw (HWND hWnd) {
        HDC        hdc = GetDC(hWnd);
        RECT    cl; GetClientRect(hWnd,&cl);

        HPEN    pen = CreatePen(PS_SOLID,0,RGB(fpen.r,fpen.g,fpen.b));
        HBRUSH    brush = CreateSolidBrush(RGB(fpen.r,fpen.g,fpen.b));
        HGDIOBJ    prev_pen = SelectObject(hdc,pen),
                prev_brush = SelectObject(hdc,brush);

        // координаты центра эллипса (самой точки)
        int    x = cl.left + x0+ p.x * scale,
            y = cl.bottom - y0 - p.y * scale;

        Ellipse (hdc, x-stroke, y-stroke, x+stroke, y+stroke);

        SelectObject(hdc,prev_pen); SelectObject(hdc,prev_brush);
        DeleteObject(pen); DeleteObject(brush);
    }

    bool Point :: PtInFigure (POINT pt) {
        float    r = (stroke * 1,3) ^ 2,
                rpt = ((p.x - pt.x) * (p.x - pt.x) + (p.y - pt.y) * (p.y - pt.y)) ^ 2;
        return rpt <= r;
    }
  • cl_Line.h
  • cl_FilledFigure.h
  • cl_Ellipse_Rectangle.h
  • f_Draw.cpp
    Код

    #include "stdafx.h"
    #include "draw.h"

    // рисуем оси OX, OY
    void DrawOXY (HWND hWnd) {
        RECT    cl;
        HDC        hdc;

        GetClientRect(hWnd, &cl);
        hdc = GetDC(hWnd);

        short    stroke = scale/10;    // длина черточки-разметки на ОХ и OY

        HFONT    font = CreateFont(scale*2/5,scale/10,3,0,0,0,0,0,0,0,0,0,0,0);
        HGDIOBJ    previous = SelectObject(hdc,font);

        // рисуем линии осей
        MoveToEx (hdc, cl.left+x0, cl.top+y0, NULL);
        LineTo (hdc, cl.left+x0, cl.bottom-y0);
        LineTo (hdc, cl.right-x0, cl.bottom-y0);

        // стрелочка на конце луча OY
        MoveToEx (hdc, cl.left+x0-stroke*2, cl.top+y0+stroke*2, NULL);
        LineTo (hdc, cl.left+x0, cl.top+y0);
        LineTo (hdc, cl.left+x0+stroke*2, cl.top+y0+stroke*2);

        // стрелочка на конце луча OX
        MoveToEx (hdc, cl.right-x0-stroke*2, cl.bottom-y0-stroke*2, NULL);
        LineTo (hdc, cl.right-x0, cl.bottom-y0);
        LineTo (hdc, cl.right-x0-stroke*2, cl.bottom-y0+stroke*2);

        char    s_char[3];
        short    s_short = 1;
        RECT    rhelp;

        // размечаем OY
        for (int i = cl.bottom-y0-scale, s_short = 1; i >= cl.top+y0+scale/2; s_short++, i -= scale) {
            MoveToEx (hdc, cl.left+x0-stroke, i, NULL);
            LineTo (hdc, cl.left+x0+stroke, i);

            SetRect (&rhelp, cl.left+x0-stroke*6, i-stroke*2, cl.left+x0-stroke*2, i+stroke*2);
            _itoa_s (s_short, s_char, 3, 10);
            DrawText (hdc, s_char, strlen(s_char), &rhelp, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
        }

        // размечаем OX
        for (int i = cl.left+x0+scale, s_short = 1; i <= cl.right-x0-scale/2; s_short++, i += scale) {
            MoveToEx (hdc, i, cl.bottom-y0-stroke, NULL);
            LineTo (hdc, i, cl.bottom-y0+stroke);

            SetRect (&rhelp, i-stroke*2, cl.bottom-y0+stroke*2, i+stroke*2, cl.bottom-y0+stroke*6);
            _itoa_s (s_short, s_char, 3, 10);
            DrawText (hdc, s_char, strlen(s_char), &rhelp, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
        }

        // ставим ноль в начале координат, подписываем лучи OX и OY
        SetRect (&rhelp, cl.left+x0-stroke*6, cl.bottom-y0+stroke*2, cl.left+x0-stroke*2, cl.bottom-y0+stroke*6);
        DrawText (hdc, "0", 1, &rhelp, DT_SINGLELINE | DT_VCENTER | DT_CENTER);

        SetRect (&rhelp, cl.left+x0-stroke*2, cl.top+y0-stroke*5, cl.left+x0+stroke*2, cl.top+y0-stroke);
        DrawText (hdc, "y", 1, &rhelp, DT_SINGLELINE | DT_VCENTER | DT_CENTER);

        SetRect (&rhelp, cl.right-x0+stroke, cl.bottom-y0-stroke*2, cl.right-x0+stroke*5, cl.bottom-y0+stroke*2);
        DrawText (hdc, "x", 1, &rhelp, DT_SINGLELINE | DT_VCENTER | DT_CENTER);

        SelectObject(hdc,previous);
        DeleteObject(font);
    }


    // рисуем координатные линии
    void DrawGrid (HWND hWnd) {
        RECT    cl;
        HDC        hdc;

        GetClientRect(hWnd, &cl);
        hdc = GetDC(hWnd);

        HPEN    pen = CreatePen(PS_DOT,1,RGB(255,0,0));
        HGDIOBJ    previous = SelectObject(hdc,pen);

        // горизонтальные координатные линии
        for (int i = cl.bottom-y0-scale; i >= cl.top+y0+scale/2; i -= scale) {
            MoveToEx (hdc, cl.left+x0, i, NULL);
            LineTo (hdc, cl.right-x0-scale/2, i);
        }

        // вертикальные координатные линии
        for (int i = cl.left+x0+scale; i <= cl.right-x0-scale/2; i += scale) {
            MoveToEx (hdc, i, cl.bottom-y0, NULL);
            LineTo (hdc, i, cl.top+y0+scale/2);
        }

        SelectObject(hdc,previous);
        DeleteObject(pen);
    }
  • Draw.h
    Код

    extern short    scale, stroke,
                    x_0, y_0;
    extern int        x0, y0;

    void DrawOXY (HWND hWnd);
    void DrawGrid (HWND hWnd);
  •  ...
Все советы из похожих тем не помогли smile smile.
P.S.. На самом деле у меня все написано очень ровно, столбик к столбику. Не знаю, почему тут все ползет во все стороны...

Автор: mes 16.7.2009, 11:49
Цитата(KasMP @  16.7.2009,  10:42 Найти цитируемый пост)
unresolved external symbol "public: virtual void __thiscall Figure::Draw(struct HWND__ *)"

забыла где то дать определение функции Figure::Draw, или если тело функции не нужно (т.е Figure позиционируется как абстрактный класс), сделать ее чисто виртуальной (=0;)

Добавлено через 2 минуты и 44 секунды
Цитата(KasMP @  16.7.2009,  10:42 Найти цитируемый пост)
extern short    scale, stroke,
                x_0, y_0;
extern int        x0, y0;

эти данные лучше оформить структурой Context и передавать как парамметр в функцию рисования.

Автор: KasMP 16.7.2009, 13:02
Цитата(mes @  16.7.2009,  11:49 Найти цитируемый пост)
забыла где то дать определение функции Figure::Draw, или если тело функции не нужно (т.е Figure позиционируется как абстрактный класс), сделать ее чисто виртуальной (=0;)

Действительно... Ты очень умный smile .
Моя беда в том, что я часто не дочитываю самые последние пункты главы... А ведь черным по белому написано (несколько раз!):
Цитата(Герберт Шилдт)

Кроме того, в некоторых ситуациях необходимо гарантировать, что виртуальня функция будет замещена во всех производных классах. Для этих ситуаций в языке С++ предусмотрены чисто виртуальные функции.

Цитата(Герберт Шилдт)

Следует иметь в виду, что все производные классы обязаны переопределять чисто виртуальную функцию. Если этого не сделать, возникнет ошибка компиляциию

Или можно поступить хитрее - просто немного определить void Figure :: Draw (HWND hWnd) (ведь хочется на всякий случай иметь возможность создавать объекты типа Figure). Хотя, такие незначащие определения ради самих определний никому не нужны и только все запутывают.
Цитата(mes @  16.7.2009,  11:49 Найти цитируемый пост)
эти данные лучше оформить структурой Context и передавать как парамметр в функцию рисования.

Тоже хорошая мысль smile smile .

Автор: mes 16.7.2009, 13:11
Цитата(KasMP @  16.7.2009,  12:02 Найти цитируемый пост)
Моя беда в том, что я часто не дочитываю самые последние пункты главы... А ведь черным по белому написано (несколько раз!):

Это несколько другая ошибка, и возникла бы если бы ты не переопределила чистую виртуальную функцию в наследниках и попыталась бы создать объект такого типа.

В твоем же случае наследники роли не играют. Ты объявила функцию, а тело не написала. В обычной функции выдало бы ошибку только в том случае, если  ты где нибудь попыталась бы ее использовать, ну а в случае с виртуальной функцией всегда (так как компилятор должен занести ее адрес в специальную таблицу.)

Добавлено через 1 минуту и 28 секунд
Цитата(KasMP @  16.7.2009,  12:02 Найти цитируемый пост)
Или можно поступить хитрее - просто немного определить void Figure :: Draw (HWND hWnd) (ведь хочется на всякий случай иметь возможность создавать объекты типа Figure). Хотя, такие незначащие определения ради самих определний никому не нужны и только все запутывают.

пока да, потом поймешь что у абстрактных классов есть свои преимущества. smile

Автор: Леопольд 16.7.2009, 14:43
Здесь я попытался разобраться со статической и динамической линковкой. Какие-то вопросы закрыл. Но классы не трогал.
http://forum.vingrad.ru/forum/topic-263893/anchor-entry1900638/0.html

Тут есть свои тонкости: 

   inline функции не подчиняются ODR, у них свои правила; 

   static функции члены линкуются динамически, в отличие от обычных  static функций. Это значит что определения  static функций членов не следует помещать в заголовочном файле, таким образом можно нарушить ODR если сделать #include в нескольких .cpp файлах проекта (как правило, это разные единицы трансляций). А определения обычных static функций можно без проблем помещать в заголовочных файлах, тогда для каждой единицы трансляции будет свой экземпляр такой функции.
Пример:

first.h
Код

#ifndef FIRST_H
#define FIRST_H
#include <iostream>

struct first{
    static void sta_mem_fun(void);
};

static char c = 'a';

static void sta_glo_fun(void){
    std::cout<<"::sta_glo_fun() - "<<c<<std::endl;
}
#endif


first.cpp
Код

#include "first.h"

void first::sta_mem_fun(void){
        std::cout<<"first::sta_mem_fun() - "<<c<<std::endl;
}


main.cpp
Код

#include "first.h"
int main() {
    c = 'b';
    first::sta_mem_fun();  //выведет first::sta_mem_fun() - a, следовательно слинковано динамически.
    sta_glo_fun(); //выведет ::sta_glo_fun() - b, виден свой экземпляр статической переменно
}


ODR - One Definition Rule (Правило Одного Определения)

Есть и другие аспекты.

Автор: zim22 16.7.2009, 16:21
Цитата(Леопольд @  16.7.2009,  14:43 Найти цитируемый пост)
. Это значит что определения  static функций членов не следует помещать в заголовочном файле, таким образом можно нарушить ODR если сделать #include в нескольких .cpp файлах проекта

почему у меня правило ODR не нарушается? 
1) определение static функции помещено в заголовочном файле
2) сделан #include в нескольких cpp файлах проекта (Bar.cpp, main.cpp)
Код

//Foo.h
#ifndef FOO_H
#define FOO_H

struct Foo {
  inline static void fcn() {}
};

#endif // FOO_H

Код

//Bar.h
#ifndef BAR_H
#define BAR_H

struct Bar {
  void bar_fcn();
};

#endif // BAR_H

Код

// Bar.cpp
#include "Bar.h"
#include "Foo.h"

void Bar::bar_fcn() {
  Foo::fcn();
}

Код

// main.cpp
#include "Foo.h"
#include "Bar.h"

int _tmain(int argc, _TCHAR* argv[])
{
  Foo f;
  f.fcn();

  Bar b;
  b.bar_fcn();

  return 0;
}



Автор: Леопольд 16.7.2009, 19:17
Цитата(zim22 @ 16.7.2009,  16:21)
почему у меня правило ODR не нарушается? 
1) определение static функции помещено в заголовочном файле
2) сделан #include в нескольких cpp файлах проекта (Bar.cpp, main.cpp)
Код

//Foo.h
#ifndef FOO_H
#define FOO_H

struct Foo {
  inline static void fcn() {}
};

#endif // FOO_H

Потому что она inline, и не только из-за квалификатора, а ещё из-за того что определение находится в теле класса. Для inline функций свои правила. Однако, inline static функции члены всё равно, линкуются динамически в отличии от inline static обычных функций, которые линкуются статически.

Похоже наврал, они видимо тоже линкуются статически... надо бы проверить в стандарте, но сейчас некогда...

А вроде и не наврал smile В страндарте ничего не нашёл, g++ 4.3.3 линкует всё же динамически. Перепроверил. Если подумать, то это логично. inline линкуются динамически, static функции члены тоже линкуются динамически, так почему же inline static функции члены должны линковаться статически?

Автор: zim22 16.7.2009, 19:22
Леопольд, не понимаю. где я сделал что-то не то?
Цитата(Леопольд @  16.7.2009,  14:43 Найти цитируемый пост)
 определения  static функций членов не следует помещать в заголовочном файле

я поместил:
Код

struct Foo {
  inline static void fcn() {}
};


Цитата(Леопольд @  16.7.2009,  14:43 Найти цитируемый пост)
таким образом можно нарушить ODR если сделать #include в нескольких .cpp файлах проекта

#include сделал в нескольких файлах
Код

// Bar.cpp
#include "Foo.h"

Код

// main.cpp
#include "Foo.h"

почему ODR не нарушается? вы же пишете, что таким образоем его можно нарушить.

Автор: Леопольд 16.7.2009, 19:27
Цитата(zim22 @ 16.7.2009,  19:22)
почему ODR не нарушается? вы же пишете, что таким образоем его можно нарушить.

Что именно непонятно из моего объяснения? smile Я уточню.

Попробуйте сделать так:
Код

struct Foo {
  static void fcn();
};

void Foo::fcn(){
}

Автор: zim22 16.7.2009, 19:36
Цитата(Леопольд @  16.7.2009,  19:27 Найти цитируемый пост)
Попробуйте сделать так:

сделал. всё компилится без ошибок. и в Студии, и в GCC

Цитата(Леопольд @  16.7.2009,  19:27 Найти цитируемый пост)
Что именно непонятно из моего объяснения?

мне непонятно не ваше объяснение, а ваше утверждение, что
Цитата(Леопольд @  16.7.2009,  14:43 Найти цитируемый пост)
можно нарушить ODR если сделать #include в нескольких .cpp файлах проекта

нарушьте его, прошу вас. smile
у меня не получается...  smile 

Автор: Леопольд 16.7.2009, 19:41
Цитата(zim22 @ 16.7.2009,  19:36)
нарушьте его, прошу вас. smile

first.h
Код

#ifndef FIRST_H
#define FIRST_H

struct first{
    static void sta_mem_fun(void);
};

void first::sta_mem_fun(void){
}

#endif


first.cpp
Код

#include "first.h"


main.cpp
Код

#include "first.h"
int main() {
    return 0;
}


Добавлено через 12 минут и 33 секунды
Цитата(zim22 @ 16.7.2009,  19:36)
у меня не получается...  smile

А у меня получается  smile 

Автор: zim22 16.7.2009, 21:01
 smile 
Цитата(Леопольд @  16.7.2009,  19:41 Найти цитируемый пост)
А у меня получается

я понял, что не понимаю, как линкуются программы в С++. лучше поздно, чем никогда...

Автор: KasMP 17.7.2009, 07:51
Цитата(mes @  16.7.2009,  13:11 Найти цитируемый пост)
В твоем же случае наследники роли не играют. Ты объявила функцию, а тело не написала. В обычной функции выдало бы ошибку только в том случае, если  ты где нибудь попыталась бы ее использовать, ну а в случае с виртуальной функцией всегда (так как компилятор должен занести ее адрес в специальную таблицу.)

Да, если заглянуть в переменную типа Point/Line/Ellipse/Rectangle во время выполнения, то в них появляется указатель на Draw() smile .
Цитата(mes @  16.7.2009,  13:11 Найти цитируемый пост)
пока да, потом поймешь что у абстрактных классов есть свои преимущества. 

Поскорей бы smile ...

Автор: Леопольд 17.7.2009, 08:36
Цитата(KasMP @ 17.7.2009,  07:51)
Цитата(mes @  16.7.2009,  13:11 Найти цитируемый пост)
пока да, потом поймешь что у абстрактных классов есть свои преимущества. 

Поскорей бы smile ...

C# - Interface. 
C++ - abstract class.

Полезность интерфейса достаточно проста. Например, у разных авто (пожарная, скорая и т.п.) разное предназначение, но есть одинаковый интерфейс - руль, педали, кпп smile В чём то они отличаются. Как правило, абстрактный базовый класс предоставляет этот общий интерфейс к потомкам. Если функция чисто виртуальная, значит эта функция должна быть переопределена в потомке (если он сам не является интерфейсом).
Имея коллекцию указателей-интерфейсов, можно "управлять" разными объектами (с этим общим интерфейсом) на которые они указывают.
Логично и то, что нельзя создать объект абстрактного класса, ведь голый интерфес, не "подключенный" ни к чему конкретному ничем не управляет.
Как правило, множественное наследование интерфейсов не создаёт проблем. Т.е. у объекта, может быть несколько разных интерфейсов одновременно.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)