顯示具有 C_and_Cpp 標籤的文章。 顯示所有文章
顯示具有 C_and_Cpp 標籤的文章。 顯示所有文章

python call C++ API by Boost~初體驗

沒有留言:
原則上,是將c++編譯成dll檔。
只是這個dll檔,是要給python用的

建立專案檔

建立一個Win32 Console的dll檔專案

原本的C++程式碼

先來看看一個cpp原本的code

.h檔

#include <string>
#include <map>

class Persion
{
    std::string m_Name;
    std::string m_HomeNumber;
public:
    Persion();
    explicit Persion(const std::string &name);
    virtual ~Persion();
    void SetName(const std::string &name);
    std::string GetName() const;
    void SetHomeNumber(const std::string &number);
    std::string GetHomeNumber() const;
};

class PersionWithCell : public Persion
{
    std::string m_CellNumber;
public:
    PersionWithCell();
    explicit PersionWithCell(const std::string &name);
    void SetCellNumber(const std::string &number);
    std::string GetCellNumber() const;
};

class PhoneBook
{
    std::map<std::string, Persion*> m_PhoneBook;
public:
    int GetSize() const;
    void AddPerson(Persion *p);
    void RemovePersion(const std::string &name);
    Persion *FindPerison(const std::string &name);
};

.cpp檔

#include "phonebook.h"

Persion::Persion():
m_Name(""), m_HomeNumber("")
{}

Persion::Persion(const std::string &name):
m_Name(name), m_HomeNumber("")
{}

void Persion::SetName(const std::string &name)
{
    m_Name = name;
}

std::string Persion::GetName() const
{
    return m_Name;
}

void Persion::SetHomeNumber(const std::string &number)
{
    m_HomeNumber = number;
}

std::string Persion::GetHomeNumber() const
{
    return m_HomeNumber;
}

Persion::~Persion()
{}

int PhoneBook::GetSize() const
{
    return (int)m_PhoneBook.size();
}

void PhoneBook::AddPerson(Persion *p)
{
    m_PhoneBook[p->GetName()] = p;
}

void PhoneBook::RemovePersion(const std::string &name)
{
    m_PhoneBook.erase(name);
}

Persion *PhoneBook::FindPerison(const std::string &name)
{
    return m_PhoneBook[name];
}

void PersionWithCell::SetCellNumber(const std::string &number)
{
    m_CellNumber = number;
}

std::string PersionWithCell::GetCellNumber() const
{
    return m_CellNumber;
}

PersionWithCell::PersionWithCell():m_CellNumber(""), Persion()
{}

PersionWithCell::PersionWithCell(const std::string &name):m_CellNumber(""), Persion(name)
{}

為C++ code 建立表皮

再來我們為了這一個cpp程式,建立一份「皮」
在此可以注意的是,如果你要為屬性設定get/set,使用.add_property(),如果是只有get或只有set,就如同新增一個function一樣,用.def()
#include "phonebook.h"
#include <boost/python.hpp>

using namespace boost::python;

static std::string PrintPersion(const Persion &p)
{
    std::ostringstream stream;
    stream << p.GetName() << ": " << p.GetHomeNumber();
    return stream.str();
}

std::ostream &operator<<(std::ostream &os, const Persion &p)
{
    os << p.GetName() << ": " << p.GetHomeNumber();
    return os;
}

BOOST_PYTHON_MODULE(phonebook)
{
    class_<Persion>("Persion", init<>())
        .def(init<std::string>())
        .add_property("name", &Persion::GetName, &Persion::SetName)
        .add_property("home_number", &Persion::GetHomeNumber, &Persion::SetHomeNumber)
        .def("__str__", &PrintPersion)
        .def(self_ns::str(self))
        ;

    class_<PhoneBook>("PhoneBook")
        .def("size", &PhoneBook::GetSize)
        .def("add_persion", &PhoneBook::AddPerson)
        .def("remove_persion", &PhoneBook::RemovePersion)
        .def("find_persion", &PhoneBook::FindPerison,
            return_value_policy<reference_existing_object>())
            ;
}


編譯

編譯成.dll檔!

用python呼叫之前


  1. 將.dll改成.pyd
  2. 將.pyd檔的檔名設定成BOOST_PYTHON_MODULE()裡定義的名字
  3. 將Boost的boost_python-vc80-mt-gd-1_60.dll copy 到和.py檔同目錄
    (這一步如果有人知道怎麼不移動檔案,還麻煩請告訴我呢!)

寫python

import debug.phonebook as phonebook

book = phonebook.PhoneBook()
p = phonebook.Persion()
p.name = "Chris"
p.home_number = '(123) 456-7890'
book.add_persion(p)
p = phonebook.Persion('Mary')
#p.name = 'Mary'
p.home_number = '(123) 456-7890'
book.add_persion(p)
print('No. of contacts =', book.size())
print(book.find_persion('Mary').home_number)
print(p)
print('--------')
import debug.phonebook as phonebook
p = phonebook.Persion('Mary')
print(p)
p.home_number = '(123) 456-7890'
print(p)

def persion_str(self):
    return "Name: %s\nHome: %s" % (self.name, self.home_number)

phonebook.Persion.__str__ = persion_str
p = phonebook.Persion()
p.name = "Chris"
p.home_number = "(123) 456-7890"
print (p)

book = phonebook.PhoneBook()

class PyPersionWithCell(phonebook.Persion):
    def get_cell_number(self):
        return self.cell
    def set_cell_number(self, n):
        cell = n
    celll_number = property(get_cell_number, set_cell_number)

p = PyPersionWithCell()
p.name = 'Martin'
p.home_number = '(123) 456-7890'
p.celll_number = '(123) 097-2134'
p2 = book.find_persion('Martin')
print(p2)

執行pythony就可以看見結果啦!

Boost C++ Libraries 哈囉~World

沒有留言:

測試環境

python 版本: python 3
visual studio版本: 2013

步驟

原則上,是依照一篇簡單明瞭的教學進行。[1]

不過因為我太嫩了,遇到一些問題無法排解。
所以,在此只記錄問題與排解的過程。有空再來把步驟補齊。

成功了就會這樣

可能會遇到的問題

  1. 問題:錯誤 1 error LNK1104: 無法開啟檔案 'python34.lib'
    解法:添加專案 lib檔路徑 C:\Python34\libs
  2. 問題:錯誤 1 error C1083: 無法開啟包含檔案: 'pyconfig.h':
    解法:添加專案 include路徑 C:\Python34\include
  3. 問題:錯誤 1 error C1083: 無法開啟包含檔案: 'stdafx.h'
    解法:註解掉教學裡,這一行程式碼「#include "stdafx.h"」,即可。

參考資料

Boost C++ Libraries 初次見面

沒有留言:
會接觸的原因是:它可以讓python用C++的code

一開始

Boost官方網站下載最新版(當下是Version 1.59.0) [1]

再來

解壓縮後,放在永久的參考目錄。(我是放在C:\BoostLib)
執行bootstrap.bat,就會出現bjam.exe

編譯release版

在command line模式,執行
bjam --build-dir=".\build" --toolset=msvc stage

編譯debug版

在command line模式,執行
bjam --build-dir=".\build" --build-type=complete --toolset=msvc stage 

hello world試看看

在visual studio,建立Win32 主控台應用程式的空專案。[2]

專案設定

專案屬性>組態屬性>C/C++>其它Include目錄: 輸入C:\BoostLib
專案屬性>組態屬性>連結器>其它程式庫目錄: 輸入C:\BoostLib\stage\lib

貼上主程式的code

#include < boost/thread/thread.hpp >  
#include < boost/bind.hpp >  
#include < iostream >  

void helloworld(const char* who)
{
    std::cout << who << ": Hello World!" << std::endl;
}

void main()
{
    boost::thread thrd(boost::bind(&helloworld, "Darkblack"));
    thrd.join();
}

編譯

若成功會顯示
Darkblack: Hello World!

參考資料

[1] boost官網
[2] 登泰山而小天下:Boost C++ Libraries 初體驗

C++單元測試(11) - OpenCppCoverage可視化單元測試的覆蓋率, Jenkins顯示Report

沒有留言:
這一次,我們來討論一下,如何將unit test的成果可視化,也就是常見的覆蓋率(Coverage rate)。

我們採用的環境如下
Visual Studio + git + CppUnit + Jenkins + OpenCppCoverage

這樣一來,在Visual Studio寫好的code提交到git後,Jenkins會輪詢git,若改版就抓一版過來;li 呼叫MSBuild編譯,編譯好就編譯CppUnit,然後執行CppUnit編好的執行檔,產生單元測試的Report,之後再執行cppcheck,產生Report,最後執行這一次的主角OpenCppCoverage,產生Report。

條列式的呈現,如下

  1. Visual Studio寫好的code提交到git後
  2. Jenkins會輪詢git,若改版就抓一版過來
  3. 呼叫MSBuild編譯主程式+編譯CppUnit
  4. 執行CppUnit編好的執行檔,產生單元測試的Report
  5. 執行cppcheck,產生Report
  6. 執行OpenCppCoverage,產生Report。

第六步,是我們今天討論的內容。

下載軟體

官方網站下載OpenCppCoverage。
安裝到Jenkins的主機上(可以先安裝在自己的電腦先測試,執行成功再裝在Jenkins的主機上)

準備Command

參考官網的文件,上面的訊息就足夠了。
自己本機測試,可以使用這樣的command
OpenCppCoverage --sources <souce code的路徑> -- <你的單元測試程式.exe> 
可以參考這篇,Jenkins上,則要這樣寫(加上--export_type=cobertura)
OpenCppCoverage --sources <souce code的路徑> --export_type=cobertura -- <你的單元測試程式.exe>

安裝Jenkins外掛

安裝Cobertura plugin

可能會遇到的問題

為什麼報表都沒有值?(Xml是空的?)

因為,需要.pdb檔。
建議使用debug模式編譯,因為除了.pdb檔本來就應該在debug模式產生之外,最好不要將code最佳化,造成無法辨別.pdb內容的情況。

所以,我在Jenkins上的設定,分別設定兩個job,一個是release編譯,一個是debug編譯。等debug編好,就執行unit test、coverage、static code analysis...,通通成功了,再編譯release。

實例

為了方便瀏覽,在此有用斷行,真正使用的指令,無需斷行,但是要加空白。
OpenCppCoverage --sources <產品程式碼路徑>
--excluded_sources <前置編譯路徑>
--excluded_sources <前置編譯.lib的.h檔 路徑>
--excluded_sources <單元測試路徑>
--excluded_sources <使用.dll, .lib檔的.h檔 路徑>
--excluded_sources <其它編譯完, 編譯器幫你用到的檔案路徑>
-- <單元測試達行檔>

hello world之 在windows建立dll檔

沒有留言:

前言

使用dll檔,有兩種,一種是要用lib加入編譯的,一種不用。
在此,是介紹不用的那種。
另外,這次的dll包裝是包裝成c語言的呼叫型式。

使用工具: visual studio 2005
環境: Windows 8

開始囉!

要準備兩個專案檔,一個是dll檔,一個是執行檔
dll檔為外掛程式,執行檔為核心程式,核心程式初始化時,dll檔就可以載進來當作是核心程式的外掛。

先說說dll檔

新增專案[1]
Project type: Win32 Console Application
Application type: DLL
Additional options: Empty project

dll_hello_world.h
這是用來設定共用介面的檔案,必須要加到核心程式的專案中,一起編譯。
extern "C" __declspec(dllexport) 
const char* HelloWorld();

dll_hello_world.cpp
 #include "dll_hello_world.h"  

__declspec(dllexport)
const char* HelloWorld()
{
    return "hello world";
}

再看看執行檔

新增專案[1]
Project type: Win32 Console Application
Application type: Console Application
Additional options: Empty project

這段程式碼,是參考[1]修改而來的,對於dll檔,我又再包了一層dll檔的類別。
每一步的行為都進行了分類,方便讀者對於dll檔的行為做區分。

main.cpp
#include "dll_hello_world.h"
#include <exception>
#include <string>
#include <iostream>
#include <Windows.h>

class DllFile
{
    typedef const char *(*CreateCallBack)();  
    CreateCallBack call_function;
    HINSTANCE dll_core;
public:
    DllFile(const std::string &dll_filename)
    try
    {
        dll_core = LoadLibrary(dll_filename.data()); //[2]
        if (dll_core == 0)
        {
            throw std::exception("load Dll not success.");
        }
    }
    catch(std::exception &e)
    {
        FreeLibrary(dll_core);
        throw ;
    }
    catch(...)
    {
        FreeLibrary(dll_core);
        throw std::exception("initial DLL false.");
    }

    ~DllFile()
    {
        FreeLibrary(dll_core);
    }

    const char* CallFunction(const std::string &symbolname)
    {
        call_function = (CreateCallBack)GetProcAddress(dll_core, symbolname.data());  
        if (call_function != 0)
        {
            return call_function();
        }
        else
        {  
            FreeLibrary(dll_core);
            throw std::exception("can not function of DLL.");
        } 
    }
};


int main()
{  
    try
    {
        DllFile demo_file("dllfile.dll");
        std::cout << demo_file.CallFunction("HelloWorld") << std::endl;
    }
    catch(std::exception &e)
    {
        std::cout << "ERROR:" << e.what() << std::endl;
    }
    system("PAUSE");

    return 0;  
}

可能會遇到的問題


  • 問題: error C2664: 'LoadLibraryW' : cannot convert parameter 1 from 'const char *' to 'LPCWSTR'
    解法: 設定專案檔的字集為Use Multi-Byte Character Set(不要用Unicode)

程式碼

放在: github (已固定版本)

參考資料

[1] [C++ 小學堂] 如何建立 export 的 dll 與如何動態呼叫 export 的 dll
[2] (原创)一个简洁通用的调用DLL函数的帮助类
[3] Error C2664 LoadLibraryW cannot convert parameter to LPCWSTR

MARCO in C++ 愈用愈好用的地方

沒有留言:
MARCO,是c語言的特性。
c++為了c也將它收進來。
c++的使用者,不一定是c高手,MARCO也就不一定用得好。
c++的使用者,不一定是c++高手,同上!XD

所以,MARCO用得讓人覺得程式怎麼愈寫愈....不開心。
是時有所聞~

MARCO包起來的,我認為是c語言時代的「程式碼片段」(比起翻譯成什麼「宏」來得好一點^^)

那麼要如何時用呢?

預防再次宣告(.h檔常用)

先看例子
#ifndef MY_CLASS_H
#define MY_CLASS_H

class MyClass
{
    //...
};
#endif

語法解釋

  • #ifndef MY_CLASS_H 如果,還沒有定義過MyClass這個字
  • #define MY_CLASS_H 定義MY_CLASS_H這個字
  • #endif 結束MACRO的if

條件編譯

程式碼參考自C++ API Design Ch3 p.68

// autotimer.h
#ifdef _WIN32
#include 
#else
#include 
#endif
#include 
class AutoTimer
{
    //...
#ifdef _WIN32
    DWORD mStartTime;
#else
    struct timeval mStartTime;
#endif
};

語法解釋

  1. #ifdef _WIN32 如果定義了_WIN32
    IDE會幫你在編譯時選定的一個參數。
    也許是_DEBUG,指的是要進入debug模式。

取代參數化的容器初始化

在此,希望程式內部使用enum取代string。
外部傳入string,在程式內部會置換成enum

enum AType
{
    A1 = 0, A2, A3, ATotal
}
map<std::string, AType> ATypeMap;
如果不使用MARCO,會麼寫。(也許有更好的寫法啦)
ATypeMap["A1test"] = A1;
ATypeMap["A2test"] = A2;
ATypeMap["A3test"] = A3;
如果使用MARCO,程式碼就可以這樣寫。
#define ADD_MAP(x) ATypeMap[#x test] = x

ADD_MAP(A1);
ADD_MAP(A2);
ADD_MAP(A3);

語法解釋

MARCO的參數[1]
  1. 參數變成字串,參數前要加上#
  2. 參數變成字元,參數前要加上#@
  3. 參數要和唯讀字串組合成新字串,參數前要加上#,並和唯讀字串隔一個空白。

參考資料:

[1] [C++ 文章收集] C++中 #define的用法 - 程式扎記

C++之static member value怎麼初始化?

沒有留言:
這個問題困擾我很久了,所以,決定把它的solution寫下來。

以免下次看到還是一樣!QQ

成員變數

MyClass.h
class MyClass
{
public:
 static int MyInt;
};
MyClass.cpp
int MyClass::MyInt= 0;

成員容器

MyClass.h
class MyClass
{
 static map<int, string> InitialMyMap();
public:
 static map<int, string> MyMap;
};
MyClass.cpp
map<int, string> MyClass::MyMap = MyClass::InitialMyMap();

map<int, string> MyClass::InitialMyMap()
{
 map<int, string> my_map;
 my_map[0] = "0";
 //....
 return my_map;
}

Jenkins的C++ 靜態程式碼分析 Cppcheck

沒有留言:
Jenkins加上靜態程式碼分析程式,真的是很棒的一件事。
所以,我也要來加!

因為我寫的是C++所以,選用了一個適合C++的程式碼分析程式 - Cppcheck
其實還有很多工具可以使用。
不過因為看見一篇文章[1]介紹,操作起來比SourceMonitor更加簡單好用,UI介面只要按一個鍵就開始分析了!哇呼!有沒有這麼簡單?!
但是其實兩個程式,不太一樣啦!
  • Cppcheck是分析編譯器無法做到的事。
  • SourceMonitor是分析程式碼複雜度之類的事。

C++單元測試(9) - CppUnit輸出Xml, Jenkins顯示Report

沒有留言:
這次的標題,下得很「關鍵字」
這次的故事是這樣的,使用CppUnit單元測試框架,使用Jenkins的CI系統+xUnitTest的plug-in。

要讓CppUnit輸出Xml,再讓xUnitTest的plug-in吃到Xml,在Jenkins上顯示。

第一步,就是先看單元測試專案的main怎麼改[1]

int main()
{
    CppUnit::TestResult testresult;
    CppUnit::TestResultCollector collectedresults;
    testresult.addListener (&collectedresults);

    CppUnit::TextUi::TestRunner runner;
    CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
    runner.addTest( registry.makeTest() );
    runner.run(testresult);

    std::ofstream xmlFileOut("CppUnitTestDmServerResults.xml");
    CppUnit::XmlOutputter xmlOut(&collectedresults, xmlFileOut);
    xmlOut.write();

    return 0;
}

主要就是要讓 testresult 放進 runner.run(testresult) 參數中。
這樣Xml輸出就會有東西了。

接下來就是設定Jenkins。

Jenkins加上xUnitTest的plug-in很多文章都有寫。就不多說了。
進入你Job的設定裡
要注意的如下

  1. 建置時要建置單元測試專案
  2. 建置後要執行單元測試的執行檔。(輸出xml)
  3. xUnitTest的plug-in要吃的,也只是xml檔。

建置時要建置單元測試專案

在Visual Studio上,就是要設定.sln檔,在某種Config時,是不是會建置呢?(要把單元測試的專案打勾唷)

建置後要執行單元測試的執行檔。

在Jos的建置,加上一個「執行Windows批次指令」,執行單元測試執行檔。
(當然如果你是Linux系統,就要加一個「執行Shell」)

xUnitTest的plug-in要吃的,也只是xml檔。

在Jos的建置後動作,加上Publish xUnit test result report
在裡面再加上一個CppUnit-1.12.1 (default)
其中Pattern的欄位,填入單元測試專案的xml路徑檔名

剩下的就照說明填囉~


參考資料: [1] Using Hudson for C++/CMake/CppUnit - Posted by volkerkaiser

Sort of C++

沒有留言:
C++的容器,是C++厲害的地方。
C++的容器,可以使用泛型演算法。
C++的容器....這是不是寫詩呀!><

C++的容器,放入指標,如何依指標指向的物件「排序」?
class aObject
{
public:
    int GetIndex() const{ return m_Index; }
    int GetScore() const{ return m_Score; }
    int m_Index;  //索引
    int m_Score;  //得分
};
//main
list<aObject*> obj_list;
//中間填入東西
obj_list.sort();
在這種情況,預設的sort是會依照指標的位址排序

這時就要使用自訂義排序方式。
不過書上教你的自訂義排序方式都不是教你排指標。
不過!這還是辦得到的唷!

宣告式如下,不過如果沒有放在class裡,就不用static
static bool SortByIndex( aObject* const m_aObject0, aObject* const m_aObject1 );
static bool SortByScore( aObject* const m_aObject0, aObject* const m_aObject1 );
實作要這樣
bool SortByIndex( aObject* const m_aObject0, aObject* const m_aObject1 )
{
    if (m_aObject0->GetIndex() >= m_aObject1->GetIndex())
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool SortByScore( aObject* const m_aObject0, aObject* const m_aObject1 )
{
    if (m_aObject0->GetScore() >= m_aObject1->GetScore())
    {
        return true;
    }
    else
    {
        return false;
    }
}
寫好這個之後,就可以囉
//main
list<aObject*> obj_list;
//中間填入東西
obj_list.sort(SortByIndex);
obj_list.sort(SortByScore);
在這種情況,預設的sort是會依照指標的位址排序

有參數的衍生類別建構子,呼叫有參數的基礎類別建構子

沒有留言:
這次要講的是「有參數的衍生類別建構子,呼叫有參數的基礎類別建構子」(有點長)

先假設一個繼承的關係如下
class Base
{
protected:
    const string m_IniFilePathName;
public:
    Base(const string& iniFilePathName): m_IniFilePathName(iniFilePathName){}
private:
    Base(){};
};

class Derivative : public Base
{
public:
    Derivative(const string& iniFilePathName);
};
在這個時候,Derivative 的建構式要怎麼設計呢?
下列來介紹幾種常見卻不可行的做法
Derivative::Derivative(const string& iniFilePathName): m_IniFilePathName(iniFilePathName){}
這樣會因為「m_IniFilePathName沒有在Derivative 宣告」而失敗。
Derivative::Derivative(const string& iniFilePathName)
{
    m_IniFilePathName = iniFilePathName;
}
這樣會除了上述的問題之讓,還會因為m_IniFilePathName 是const而「無法進行賦值,只能初始化」而失敗。

而且這樣設計還有一個重點,就是無法不初始化基礎類別(Base)。
因為Derivative的建構子會預設使用Base的無參數建構子。
但是在此Base的無參數建構子設成private,所以就算Derivative放棄用參數建構也是不行。

真正要解決這件事怎辦?
Derivative::Derivative(const string& iniFilePathName): Base(iniFilePathName){}
只要這麼做,就可以使用有參數的基礎類別囉!基礎類別建構子中初始化過的變數,也就不用再貼過來衍生類別的建構子囉。

pimpl完全的資訊隱藏

沒有留言:
pimpl(pointer to implementation), 指向實作的指標。 這是《API Design for C++》Ch3.1的心得整理。
在此是要介紹,在C++中,如何實現「資訊隱藏狂熱」,class裡完全的將public以外的東西隱藏掉,在API設計中,這是很重要的,避免錯誤的使用,也讓設計更簡單好記。

在此,作者也有提到Effective C++ #34也有提及這個技巧。
無獨有偶的,Code Complete 2/e中也有提到。不過在《API Design for C++》中有強調,這是C++獨有的技巧,所以不算是通用的Design Pattern,不過,算是很厲害的Design Pattern for C++。

(在此,不使用書中的範例程式)
這個例子,是隱藏.cpp裡一切細節的範例程式的延伸版。
類別要描述的是「不會透露自己的年紀的人」。
透過pimpl技巧,強調「不會透露自己的年紀的人,更不會跟別人透露不想提及的意圖」。
//Person.h
#include <string>
#include "date.h"
#include "address.h"

class Person
{
    std::string theName;
    Date theBirthDate;
    Address theAddress;
    int GetYears(int currYear);
public:
    Person();
    ~Person();
    Person(const std::string& name,
           const Date& birthday,

    std::string name() const;
    std::string birthDate() const;
    std::string address() const;
    //...
};

極致的資訊隱藏手法

我們希望它可以只露出必要的部份,所以將它改寫成這樣
//Person.h
#include <string>
#include "date.h"
#include "address.h"

class Person
{
    class PersonImpl;   //如果使用上造成太多存取的限制,可以考慮將這一行改成public
    PersonImpl* pImpl;  //宣告一個實作類別的指標(或參考也行,就是不可以是實體)
public:
    Person();
    ~Person();
    Person(const std::string& name, 
           const Date& birthday,
           const Address& addr);

    std::string name() const;
    std::string birthDate() const;
    std::string address() const;
    //...
};
//Person.cpp
#include "Person.h"

class Person::PersonImpl
{
public:
    std::string theName;
    Date theBirthDate;    
    Address theAddress;

    int GetYears(int currYear)
    {
        return currYear - BirthDate.Year();
    }
};

Person::Person(const std::string& name, 
               const Date& birthday,
               const Address& addr):
pImpl(new PersonImpl())
{
    pImpl->theName = name;
    pImpl->theBirthDate = birthday;
    pImpl->theAddress = addr;
};

Person::~Person()
{
    delete pImpl;
    pImpl = 0;
}
除此之外,對於類別的複製建構式與賦值運算子的override都是必須要注意的實作細節唷。

使用Smart Pointer

書裡還建議使用smart pointer避免使用這種方式時,pimpl實作不見了的情況。
//Person.h
#include <personimpl>
#include "date.h"
#include "address.h"

class Person
{
    class PersonImpl;   //如果使用上造成太多存取的限制,可以考慮將這一行改成public
    std::unique_ptr pImpl;  //使用適合的Smart Pointer
public:
    Person();
    ~Person();
    explicit Person(const std::string& name, 
           const Date& birthday,
           const Address& addr);

    std::string name() const;
    std::string birthDate() const;
    std::string address() const;
    //...
};
  • shared_ptr 指向相同的物件,誤刪不消失。 
  • scoped_ptr 保證唯一,無法複製。

pImpl的優點

  • Information Hidding
  • 降低耦合
  • 加快編譯
  • ...

pImpl的缺點

  • 增加一點點的物件大小
  • 降低程式碼可讀性
  • 提高維護程本(除錯時要追程式碼就困難許多了)
  • 類別中的const函數,無法保證private的成員變數唯讀(只保證pImpl的指標不改變)

而這篇就是紀錄書中,如何改寫的注意事項。
詳細的細節,還是去看書吧!^^這本書很棒唷!

參考資料: 

[1] 3.1 Pimpl慣用語 - C++ API 設計 

聖誕節快樂!!!

沒有留言:
軟體工程師的浪漫呀~~
http://codepad.org/
貼上,選C++
#include <iostream>
using namespace std;
int main()
{
    const int hTree = 50;
    const int wTree = hTree/2;
    for (int ih = 0; ih < hTree; ++ih)
    {
        int shift = ih/8*3;
        for (int i = 0; i < hTree - ih + shift; ++i)
            cout << " ";
        for (int i = 0; i < ih+ih+1 - shift*2; ++i)
            cout << "*";
        cout << endl;
    }
    return 0;
}

不要再忘記的function pointer

沒有留言:
先貼上一段程式碼(參考自K&R2 Ch5.11)
#include <stdio.h>
  
typedef int (*returnInt)(void*, void*);
  
struct A
{
    returnInt comp;   
};

int numcmp(char* s1, char* s2)
{
    double v1, v2;

    v1 = atof(s1);
    v2 = atof(s2);

    if (v1 < v2)
        return -1;
    else
        return 0;
}

int main()
{
    struct A a;
    a.comp = numcmp;

    int o = a.comp("6", "5");
    printf("%d", o);

    return 0;
}
輸出結果:
0

行3:用typedef來宣告函數指標(function pointer),之後只要寫retutnInt,就算是寫整個函數指標,不用每一個地方都寫完整的函數指標宣告式。
function pointer的型別,包含它的返回值與參數列。
function pointer的名稱,就是...它的名稱。
用typedef的話,是另外取一個名稱代替它的型別,所以第7行才可以只寫名稱不寫完整的宣告式。

行7:在struct A中,有一個函數指標(function pointer),可以把它當作是c#的委派宣告。(因為函數的返回值與參數必須相同)
行26:指定要執行的function(在此尚未執行)
行28:使用方式,像C++的member function直覺。執行指定的function。

C++沉思錄//第四章整理

沒有留言:

class design checklist

  • 你需要一個建構子嗎?
想一下。
  • 你的成員變數是私有的嗎?
以函數當作存取成員變數的好處:
1. 定義域、值域的程式化。ex: length的長度一定要大於零。
2. 算式表示抽象屬性。ex: vector<int>::length(); 元素的數量是取值時才運算出來的。
  • 你的類別需要一個無參數的建構子嗎?
利用無參數建構子用來定義成員變數的「預設初始化狀態」。
  • 是不是每個建構子都初始化所有的成員變數呢?
雖然建構子是初始化所有的成員變數,但其實在真實世界的例子中,也不是這麼一定。
這問題是刺激你思考是否都盡可能的初始化了。
  • 類別需要解構子嗎?
思考:
1. 這個類別要做些什麼
2. 是否有不會由成員函數自動釋放的動態記憶體空間
通常,建構子有new出什麼動態記憶體配置,解構子就要delete
  • 類別需要一個虚擬解構子嗎?
動態連結時,基礎類別指標是否有必要執行洐生類別的物件的解構子,為了釋放洐生類別的物件才有使用的動態記憶體空間
  • 你的類別需要自己寫一個複製建構子嗎?
複製該類別的物件,不相當於「複製其成員變數和基礎類別物件」,則需要複製建構子。
如果有動態記憶體宣告的空間,就考慮是否要複製建構子。
  • 你的類別需要自己寫一個賦值運算子嗎?
如果有複製建構子,大多需要建立一個。
注意返回值要 X& X::operator=(),並且 return *this;
  • 你的賦值運算子能正確的將物件賦值給物件嗎?
「自我賦值常常被錯誤的應用,不只是一本C++的書弄錯了」
賦值總是用新的值取代舊的值,但是如果「來源物件與目標物件是同一個」,就不可以奉行「先釋放舊值,再複製新值」,會先毀掉來源物件裡的值。
在這,有兩個作法

1. 判斷是不是賦值給自己,再決定是不是要delete
xString$ xString::operator=(const xString& xstr)
{
    if (&s != this)
    {
        delete [] data;
        data = new char[strlen(s.data) + 1];
        strcpy(data, s.data);
    }
    return *this;
}
2. 先暫存,再賦值,最後再delete
xString$ xString::operator=(const xString& xstr)
{

    char* newdata = new char[strlen(s.data) + 1];
    strcpy(newdata, s.data);
    delete [] data;
    data = newdata;    
    return *this;
}
  • 你的類別需要關係運算子嗎?
只要用戶想要排序你的類,你就必須要提供關係運算子。
  • 刪除陣列時你記得使用delete[]嗎?
會保留這種奇怪的寫法,是為了和C語言相容,同時保有效率。

  • 記得在複製建構子和賦值運算子的參數加上const了嗎?
提供保證,複製物件並不會改變原物件。
  • 如果函數的參數是參考,它們是否該加上const?
只有當函數想改變參數時,才會取消加上const
  • 記得適當的宣告成員函數成const了嗎?
有設定唯讀的函數,才可以套用在STL的演算法中執行。

VS2005 msvcprtd.lib(MSVCP80D.dll) : error LNK2005

沒有留言:
編譯完出現下面的訊息

1>msvcprtd.lib(MSVCP80D.dll) : error LNK2005: "public: __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::~basic_string<char,struct std::char_traits<char>,class std::allocator<char> >(void)" (??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ) 已在 cppUnitMain.obj 中定義過了

1>msvcprtd.lib(MSVCP80D.dll) : error LNK2005: "public: __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)" (??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@ABV01@@Z) 已在 cppUnitMain.obj 中定義過了

1>msvcprtd.lib(MSVCP80D.dll) : error LNK2005: "public: char const * __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::c_str(void)const " (?c_str@?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QBEPBDXZ) 已在 cppUnitMain.obj 中定義過了

1>MSVCRTD.lib(ti_inst.obj) : error LNK2005: "private: __thiscall type_info::type_info(class type_info const &)" (??0type_info@@AAE@ABV0@@Z) 已在 LIBCMT.lib(typinfo.obj) 中定義過了

1>MSVCRTD.lib(ti_inst.obj) : error LNK2005: "private: class type_info & __thiscall type_info::operator=(class type_info const &)" (??4type_info@@AAEAAV0@ABV0@@Z) 已在 LIBCMT.lib(typinfo.obj) 中定義過了
1>MSVCRTD.lib(MSVCR80D.dll) : error LNK2005: "public: virtual __thiscall std::exception::~exception(void)" (??1exception@std@@UAE@XZ) 已在 LIBCMT.lib(stdexcpt.obj) 中定義過了

1>MSVCRTD.lib(MSVCR80D.dll) : error LNK2005: "public: __thiscall std::exception::exception(class std::exception const &)" (??0exception@std@@QAE@ABV01@@Z) 已在 LIBCMT.lib(stdexcpt.obj) 中定義過了
1>MSVCRTD.lib(MSVCR80D.dll) : error LNK2005: "public: __thiscall std::exception::exception(void)" (??0exception@std@@QAE@XZ) 已在 LIBCMT.lib(stdexcpt.obj) 中定義過了

1>LINK : warning LNK4098: 預設的程式庫 'MSVCRTD' 與其他使用的程式庫衝突,請使用 /NODEFAULTLIB:library


[1] 只要把編譯的RTTI打開就可以解決了!
[2] vs2005 專案檔設定>C/C++>Code Generation>RunTime Library,選Muti-threaded Debug DLL(參考)

參考資料:
[1] How to resolve linking error?
[2] VS2005中運行時庫不一致導致項目編譯出問題

C++單元測試(4) - 用VC6先試一下: 如何使用CppUnit

沒有留言:
接續前篇,因為暫時還不想換tool寫文章!^^
就繼續VC6的使用說明。

在這裡,我們為了要先測試一下編出來的檔案對不對。
就暫時先以CppUnit CookBook中範例的最終版本當sample code吧!
為了方便之後在VS2010上做測試,就先把VC6可以跑的code貼在這。

由於UnitTest是獨立在開發專案外的另一個專案。
所以,會在下面的圖中,看見兩個專案檔,這是正常,未來上手之後也都是這樣安排的。
(不然就不叫unit test了,就用條件編譯就好啦!)

專案檔與檔案之間的設定

先看專案檔與檔案之間的設定
(此專案檔都是Win32 Console Application)

程式碼

接下來就是貼source code
先複製到各個檔案上面。
main
#include <iostream>
#include "Complex.h"

using namespace std;

int main()
{
    Complex a(1, 3);
    Complex b(1, 3);
    Complex c = a + b;

    cout << c.real << endl;
    return 0;
}
Complex.h
#ifndef COMPLEX_H
#define COMPLEX_H

class Complex
{ 
    friend bool operator==(const Complex& a, const Complex& b);
    friend Complex& operator+(const Complex &a, const Complex &b);
    friend Complex& operator/(const Complex& a, const Complex& b);
public:  //just for test
    double real, imaginary;
public:
    Complex( double r, double i = 0 ): real(r), imaginary(i) { }
};

bool operator==( const Complex &a, const Complex &b )
{ return (a.real == b.real)&&(a.imaginary == b.imaginary); }
Complex& operator+(const Complex &a, const Complex &b )
{ return *(new Complex(a.real + b.real, a.imaginary + b.imaginary)); }
Complex& operator/(const Complex &a, const Complex &b )
{ return *(new Complex(a.real / b.real, a.imaginary / b.imaginary)); }

#endif
cppUnitMain.cpp
#include "cppUnitLib.h"
#include "ComplexNumberTest.h"
#include <iostream>

int main()
{
    CppUnit::TextUi::TestRunner runner;
    CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
    runner.addTest( registry.makeTest() );
    runner.run();

    return 0;
}
ComplexNumberTest.h
#ifndef ComplexNumberTest_H
#define ComplexNumberTest_H
#include "cppUnitLib.h"
#include "../Complex.h"

class ComplexNumberTest : public CppUnit::TestFixture
{
private:
    Complex *m_10_1, *m_1_1, *m_11_2;
public:
    void setUp()
    {
        m_10_1 = new Complex(10, 1);
        m_1_1  = new Complex( 1, 1);
        m_11_2 = new Complex(11, 2);
    }

    void tearDown()
    {
        delete m_10_1;
        delete m_1_1;
        delete m_11_2;
    }

    void testEquality()
    {
        CPPUNIT_ASSERT( *m_10_1 == *m_10_1 );
        CPPUNIT_ASSERT( *m_10_1 == *m_11_2 );
    }

    void testAddition()
    {
        CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
    }

    void testDivideByZeroThrows()
    {
        *m_10_1 / Complex(0);
    }
    CPPUNIT_TEST_SUITE( ComplexNumberTest );
    CPPUNIT_TEST( testEquality );
    CPPUNIT_TEST( testAddition );
    CPPUNIT_TEST_EXCEPTION( testDivideByZeroThrows, std::exception );
    CPPUNIT_TEST_SUITE_END();
};
CPPUNIT_TEST_SUITE_REGISTRATION( ComplexNumberTest );

#endif
cppUnitLib.h
#ifndef CPPUNIT_LIB
#define CPPUNIT_LIB
#include <cppunit/TestCase.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/XmlOutputter.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/TestResult.h>
#endif

設定專案檔屬性

最後,再來設定專案檔,如何使用CppUnit的檔案。

.h檔路徑

先設定要#include 的.h檔路徑
在Additional include directiories:中填上.h檔路徑

lib檔,檔案名稱及路徑

再設定link時期要找得到的lib檔,檔案名稱及路徑
在Object/library modules: 中填入.lib檔的檔名
在Additional library path: 中填入.lib檔放置的路徑

執行

設定UnitTest為Active Project就可以「以Unit Test為主」
在IDE按下編譯→執行,就可以看見Unit Test的執行結果報告了!^^


C++單元測試(3) - 測試VS2005的C++專案

沒有留言:
前一篇取得的聖杯。

接下來的困難點,卻是VS2005的專案檔設定。

這一次的環境是使用Windows 8.1, VS2005 SP2
CppUnit要修改專案檔

不過!要知道取出哪一個部份

第一步 取得正確的檔案

要知道我們在上一篇編譯結束後,出現的cppunit.lib 和 cppunit_dll.lib要如何正確使用。
除了擁有link時期需要的.lib檔之外,還要取得complier時期需要的.h檔(也就是include會用到的部份)。

.lib檔位於cppunit1.13.2/lib/裡面。
.h檔位於cppunit1.13.2/include/裡面。(通常會編出Debug和Release兩種)

不過,我沒有試過直接拿CppUnit的原始碼加到專案檔裡,這招應該是沒有問題的。
下次來試試看。

第二步,開啟你要做單元測試的專案

在此就是使用VS2005啦。
其實,難還是難在專案檔的設定,因為沒有任何書針對這部份詳細解說(入門書都簡單講一下,深入的書都不提這個區塊,MSDN....你懂的!)

在此,我們使用的專案名稱為「xCppUnit」
並且把cppUnit的檔案另外放在D:\sandbox\cppunitlib (不放在上圖的目錄中)
分別是
cppunitlib\include\cppunit\extensions
cppunitlib\include\cppunit
cppunitlib\include

在專案設定裡找到這個地方,並填上.h檔的路徑(記得用;隔開,設定不包含子目錄吧?)

在專案設定裡找到這個地方,並填上.lib檔的路徑(記得用;隔開)

第三步 貼上練習的code,Build it!!

接下來就完事了。
可以開始寫測試案例了貼上練習的測試案例測試看看囉![1]

只有一個main檔。沒有加上其它的code。貼上測試案例,加上足夠的include檔案。即可編譯成功。

這系列到目前,是我遇到網路上找不到資料的步驟,也許太簡單所以高手寫文章都直接跳過這些,對於我們這種新手要踏進來,對於太多不懂的。所以,就將自己研究的過程寫下來,並且仔細的告訴你,怎麼做,祝大家順利啦!^^。

另外,有一個延伸閱讀。

第一篇的時候,我們找到了維基百科對CppUnit的參考資料
上面有一個Further reading,這是一本教你寫遊戲的書唷!^^
Ch1.7有介紹CppUnit的使用方式,有興趣的人可以把它找出來。[2]


參考資料:
[1] CppUnit CookBook 中文版
[2] 游戏编程精粹6

C++單元測試(2) - 用VC6編譯CppUnit

沒有留言:
接續前一編
「事不疑遲,快到freedesktop下載最新版吧!」

這一版的CppUnit,自帶VC6和VS2010的兩個不同的VS專案檔,這一篇就先介紹使用VC6的路上要突破的重重挫折。

使用環境
Windows 7
Visual C++ 6
  1. 安裝Visual C++ 6,git for windows
  2. clone CppUnit,並且切換到cppunit-1-13的分支。
    master的PlugInManager.cpp檔Line:3會出現「找不到stdint.h」的error
    而這個分支,直接砍掉出問題的這一行.....
    $git clone git://anongit.freedesktop.org/git/libreoffice/cppunit/
    %(git dir)$git checkout cppunit-1-13
  3. 再打開 src/CppUnitLibraries.dsw
  4. 編譯 每一個 專案
    編譯條件有四種,通通都要編一下Win32 Release, Win32 Debug, Win32 Release Unicode, Win32 Debug Unicode 對每一個專案檔進行各別編譯 Build(selection only)
    順序如下試一下就知道了。
    這一步會出現一些error,得慢慢的一個一個解。(下面有一些可以參考的經驗)
  5. 在lib/可以找到編譯好的檔案
    我編出來有23個檔案。

編譯出現error

  • cdxCDynamicDialog.cpp(30) : error C2440 這是timer的參數型別出問題。
    只要把
    void cdxCDynamicDialog::OnTimer(UINT_PTR idEvent)
    改成
    void cdxCDynamicDialog::OnTimer(UINT idEvent)
  • TreeHierarchyDlg.cpp(106) : error C2065
    m_treeTests.SetItemData(...) 參數型別出問題。(改法類似上一點)
  • TreeHierarchyDlg.cpp(188) : error C2065
    Text::data; 型別出問題(改法類似上一點)
這樣就已經取得聖杯了。
剩下的就是怎麼使用它了!下次我們再來介紹吧!