下了一些百度webapi代碼,定義聲明都在.h文件中,沒有.cpp文件。
我的代碼是.h .cpp分開的,當(dāng)我包含那些.h后,編譯就報(bào)錯(cuò)一堆重復(fù)定義
下載連接地址:百度在線語音識(shí)別下載鏈接
——————————————————————————————
好像圖片顯示不了,粘貼一段異常
g++ main.cpp RecogBaiduOnline.cpp -L. -ljsoncpp -lcrypto -lcurl -g -o main -std=c++11
/tmp/ccVleVa4.o:在函數(shù)‘void std::_Destroy_aux<false>::__destroy<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*)’中:
/home/dyan/projects/recognition_sound/base/base64.h:35: `aip::base64_encode[abi:cxx11](char const*, unsigned int)'被多次定義
/tmp/cchpsjly.o:/home/dyan/projects/recognition_sound/base/base64.h:35:第一次在此定義
/tmp/ccVleVa4.o:在函數(shù)‘void std::__adjust_heap<__gnu_cxx::__normal_iterator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, __gnu_cxx::__ops::_Iter_less_iter>(__gnu_cxx::__normal_iterator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, long, long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, __gnu_cxx::__ops::_Iter_less_iter)’中:
/home/dyan/projects/recognition_sound/base/base64.h:88: `aip::base64_decode(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'被多次定義
/tmp/cchpsjly.o:/home/dyan/projects/recognition_sound/base/base64.h:88:第一次在此定義
/tmp/ccVleVa4.o:在函數(shù)‘a(chǎn)ip::utc_time[abi:cxx11](long)’中:
/home/dyan/projects/recognition_sound/base/utils.h:79: `aip::utc_time[abi:cxx11](long)'被多次定義
/tmp/cchpsjly.o:/home/dyan/projects/recognition_sound/base/utils.h:79:第一次在此定義
—————————————————————————
把源碼中重復(fù)定義的函數(shù)都加了static inline,只保留了第一個(gè)重復(fù)定義函數(shù)沒有加inline用來顯示錯(cuò)誤,加了g++ -v參數(shù),完整編譯信息
g++ main.cpp RecogBaiduOnline.cpp -L. -ljsoncpp -lcrypto -lcurl -g -v -o main -std=c++11
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.9' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9)
COLLECT_GCC_OPTIONS='-L.' '-g' '-v' '-o' 'main' '-std=c++11' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/5/cc1plus -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE main.cpp -quiet -dumpbase main.cpp -mtune=generic -march=x86-64 -auxbase main -g -std=c++11 -version -fstack-protector-strong -Wformat -Wformat-security -o /tmp/ccQmpXV5.s
GNU C++11 (Ubuntu 5.4.0-6ubuntu1~16.04.9) version 5.4.0 20160609 (x86_64-linux-gnu)
compiled by GNU C version 5.4.0 20160609, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/5"
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/5/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/include/c++/5
/usr/include/x86_64-linux-gnu/c++/5
/usr/include/c++/5/backward
/usr/lib/gcc/x86_64-linux-gnu/5/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/5/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
GNU C++11 (Ubuntu 5.4.0-6ubuntu1~16.04.9) version 5.4.0 20160609 (x86_64-linux-gnu)
compiled by GNU C version 5.4.0 20160609, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: d8217bc73be730fa402b500d5726a5b4
COLLECT_GCC_OPTIONS='-L.' '-g' '-v' '-o' 'main' '-std=c++11' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
as -v --64 -o /tmp/ccPnErzo.o /tmp/ccQmpXV5.s
GNU匯編版本 2.26.1 (x86_64-linux-gnu) 使用BFD版本 (GNU Binutils for Ubuntu) 2.26.1
COLLECT_GCC_OPTIONS='-L.' '-g' '-v' '-o' 'main' '-std=c++11' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/5/cc1plus -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE RecogBaiduOnline.cpp -quiet -dumpbase RecogBaiduOnline.cpp -mtune=generic -march=x86-64 -auxbase RecogBaiduOnline -g -std=c++11 -version -fstack-protector-strong -Wformat -Wformat-security -o /tmp/ccQmpXV5.s
GNU C++11 (Ubuntu 5.4.0-6ubuntu1~16.04.9) version 5.4.0 20160609 (x86_64-linux-gnu)
compiled by GNU C version 5.4.0 20160609, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/5"
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/5/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/include/c++/5
/usr/include/x86_64-linux-gnu/c++/5
/usr/include/c++/5/backward
/usr/lib/gcc/x86_64-linux-gnu/5/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/5/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
GNU C++11 (Ubuntu 5.4.0-6ubuntu1~16.04.9) version 5.4.0 20160609 (x86_64-linux-gnu)
compiled by GNU C version 5.4.0 20160609, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: d8217bc73be730fa402b500d5726a5b4
COLLECT_GCC_OPTIONS='-L.' '-g' '-v' '-o' 'main' '-std=c++11' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
as -v --64 -o /tmp/ccCqvBxL.o /tmp/ccQmpXV5.s
GNU匯編版本 2.26.1 (x86_64-linux-gnu) 使用BFD版本 (GNU Binutils for Ubuntu) 2.26.1
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-L.' '-g' '-v' '-o' 'main' '-std=c++11' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/5/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper -plugin-opt=-fresolution=/tmp/ccPnBSQ8.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro -o main /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o -L. -L/usr/lib/gcc/x86_64-linux-gnu/5 -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/5/../../.. /tmp/ccPnErzo.o /tmp/ccCqvBxL.o -ljsoncpp -lcrypto -lcurl -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
/tmp/ccCqvBxL.o:在函數(shù)‘__gnu_cxx::new_allocator<ASR_result*>::new_allocator()’中:
/home/dyan/projects/recognition_sound/base/base64.h:35: `aip::base64_encode[abi:cxx11](char const*, unsigned int)'被多次定義
/tmp/ccPnErzo.o:/home/dyan/projects/recognition_sound/base/base64.h:35:第一次在此定義
collect2: error: ld returned 1 exit status
makefile:9: recipe for target 'main' failed
make: *** [main] Error 1
——————————————————————————————
然后看了下代碼,有預(yù)編譯,不是重復(fù)包含的問題。只看前2個(gè)報(bào)的重復(fù)定義35行和88行
之后我也把自己的聲明和定義都寫到.h文件中并include百度的.h,沒有了.cpp文件,異常就消失了。
問題:
1.這樣做就讓我之后include的.h文件中只要有include百度的.h就全部要做成這樣嗎?這很不現(xiàn)實(shí)啊。
2.這種異常是如何產(chǎn)生的?
3.如何解決?
使用你提供的 SDK 下載鏈接,親測(cè)可編譯(linux x64 g++)。
源碼目錄結(jié)構(gòu)如下
.
├── a.cpp
├── a.h
├── aip-cpp-sdk-0.4.0
│?? ├── base
│?? │?? ├── base64.h
│?? │?? ├── base.h
│?? │?? ├── http.h
│?? │?? └── utils.h
│?? ├── face.h
│?? ├── image_censor.h
│?? ├── image_classify.h
│?? ├── image_search.h
│?? ├── kg.h
│?? ├── nlp.h
│?? ├── ocr.h
│?? ├── README.md
│?? └── speech.h
├── main.cpp
└── Makefile
文件 a.h 內(nèi)容如下
#ifndef _a_h_
#define _a_h_
bool call_speech();
#endif
文件 a.cpp 內(nèi)容如下
#include "aip-cpp-sdk-0.4.0/speech.h"
#include <json/json.h>
bool call_speech() {
aip::Speech sp("app_id", "ak", "sk");
Json::Value data;
Json::Value result = sp.request_asr("url", data);
return result.isString();
}
文件 main.cpp 內(nèi)容如下
#include "a.h"
int main()
{
return call_speech() ? 0: 1;
}
文件 Makefile 內(nèi)容如下
demo: a.cpp main.cpp
g++ -Wall -std=c++11 -g $^ -o $@ -lcurl -lcrypto -ljsoncpp
使用命令 make 順利編譯。
由于你沒有貼出調(diào)用 SDK 的相關(guān)代碼,我無法準(zhǔn)確判斷原因。
以上面的代碼結(jié)構(gòu)為例,你很可能在 a.h 里引用了 SDK 的頭文件,然后在 a.cpp 里面引用了 a.h 頭文件,類似這樣
// a.h 文件內(nèi)容
#ifndef _a_h_
#define _a_h_
#include "aip-cpp-sdk-0.4.0/speech.h"
#include <json/json.h>
bool call_speech();
#endif
// a.cpp 文件內(nèi)容
#include "a.h"
#include <json/json.h>
bool call_speech() {
.......
這種寫法就會(huì)出現(xiàn) “重復(fù)定義” 的錯(cuò)誤,。
SDK 下載地址: http://ai.baidu.com/download?...
瀉藥, @李毅 老大已經(jīng)點(diǎn)名你出錯(cuò)的地方了, @felix 老大也指出是ODR的問題, 看來窩除了能在上面說下原理沒什麼做了, 哈哈. 不過既然兩位老大都沒有將原理和你的庫結(jié)合, 那麼這個(gè)微小工作就由窩踩在兩位老大的肩膀上來完成吧.
窩給你結(jié)合標(biāo)準(zhǔn)文檔從頭梳理一些c++裏面必須知道的入門常識(shí)性概念, 這些概念可能你會(huì)覺得過有點(diǎn)多餘, 但是這些是每一個(gè)寫c++的必須知道的基礎(chǔ). 並且窩在這裏會(huì)剔除些標(biāo)準(zhǔn)裏的wording, 只會(huì)提及此處需要用到的概念.
以下引用皆出自N4741, 歌詞大意可以理解爲(wèi)Informally的簡略版解釋(可能有錯(cuò)誤的私貨, 如有老大看出, 望告知)
這在標(biāo)準(zhǔn)裏面沒有, 只是c++程序員爲(wèi)了實(shí)現(xiàn)ODR的一個(gè)慣用法罷了, 通過conditional inclusion在頭文件裏面定義大部分時(shí)間都可以用#pragma once 來代替, 不過窩有一次被一個(gè)大佬警告過其可移植性, 但是窩查了下發(fā)現(xiàn)msvc, clang, gcc, icc, xl都zici呀...可能是有標(biāo)準(zhǔn)潔癖吧.
The text of the program is kept in units called source files in this document. A source file together with all the headers (20.5.1.2) and source files included (19.2) via the preprocessing directive#include, less any source lines skipped by any of the conditional inclusion (19.1) preprocessing directives, is called a translation unit.
歌詞大意: 每一個(gè)源文件(.cpp/.cc/.cxx等)在使用了include guard後展開頭文件(.hh/.hpp/.h等)(即複製頭文件所有內(nèi)容進(jìn)源文件)
Each entity declared by a declaration is also defined by that declaration, unless..........
歌詞大意, $$\mathsf{definitions} \subset \mathsf{declarations}$$, 這就是這兩者的關(guān)係, 不過窩在某個(gè)dlang的群裏發(fā)現(xiàn)很多寫了c++多年的選手依然不知道這一點(diǎn), 這是很致命的, 比如不熟悉其區(qū)別可能就會(huì)產(chǎn)生窩這樣的困惑:https://stackoverflow.com/que... 還可能有其它危害, 下文也會(huì)提及.
在n4741中, ODR分爲(wèi)了12大點(diǎn), 以後還可能擴(kuò)充或修改, 前些日子在so上看到一個(gè)語言律師在odr-use上發(fā)現(xiàn)了自相矛盾的地方. 不過這裏只需要說下兩點(diǎn)就行了.
1) No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, or template.
本來不想說odr-used的, 但是發(fā)現(xiàn)還是脫不開它.
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (7.1) to x yields a constant expression (8.6) that does not invoke any non-trivial functions and, if x is an object,ex is an element of the set of potential results of an expressione, where either the lvalue-to-rvalue conversion (7.1) is applied toe, or e is a discarded-value expression (8.2).
歌詞大意: 先要瞭解potentially-evaluated expression是什麼, 花個(gè)1分鐘看下這個(gè)帖子: https://stackoverflow.com/que... 好, 當(dāng)ex滿足potentially-evaluated expression的性質(zhì)時(shí), 除非做了左值->右值的轉(zhuǎn)換(如x作爲(wèi)返回值, 但是是按值返回的), 亦或是x本身不是object, 比如x可以是引用(另一種情況這不解釋了, 不然牽扯的概念就太多了). 好, 說這些可能會(huì)有點(diǎn)混, 其實(shí), 你需要知道的是odr-use的意思就是如字面所說--需要definition的存在, 而不僅僅是declaration(現(xiàn)在你應(yīng)該明白爲(wèi)什麼窩在一開始就要區(qū)分definition和declaration及其子集關(guān)係了吧).
繼續(xù)回到One definition rule的定義
10) Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement (9.4.1); no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 15.1, 15.4 and 15.8). An inline function or variable shall be defined in every translation unit in which it is odr-used outside of a discarded statement.
歌詞大意: 對(duì)於函數(shù)或者變量, 在整個(gè)程序中也要遵循odr原則, 但是很明顯頭文件會(huì)被很多源文件包含, 該怎麼辦呢? inline用來開洞解決這個(gè)問題, 加了inline, 對(duì)函數(shù)的odr檢查就被無視了(當(dāng)然, 很多時(shí)候會(huì)自動(dòng)inline, 比如類內(nèi)定義, friend等).
窩不知道你自己的程式結(jié)構(gòu), 那麼我就沿用@李毅 老大給出的文件組織了:
├── a.cpp
├── a.h
├── aip-cpp-sdk-0.4.0
│ ├── base
│ │ ├── base64.h
│ │ ├── base.h
│ │ ├── http.h
│ │ └── utils.h
│ ├── face.h
│ ├── image_censor.h
│ ├── image_classify.h
│ ├── image_search.h
│ ├── kg.h
│ ├── nlp.h
│ ├── ocr.h
│ ├── README.md
│ └── speech.h
├── main.cpp
└── Makefile
很明顯, translation parsion之後我們由a.cpp和main.cpp兩個(gè)翻譯單元存在, 好, 我們通過展開這兩個(gè)翻譯單元來分析爲(wèi)什麼你自己寫的(即版本二)會(huì)違背ODR, 而@李毅 老大給出的第一個(gè)版本就不會(huì). 爲(wèi)了進(jìn)一步簡化問題, 我們把#include <json/json.h>不予考慮, 規(guī)定base/base.h的內(nèi)容除去include guard僅有#include "base64.h一行, base/base64.h除去include guard有
include "iostream"
namespace aip { void f() { std::cout << "hello"; }
speech.h除去include guard有
#include "base/base.h"
namespace aip {
void f_s() {
aip::f_i();
}
}
main.c有:
#include "a.h"
int main()
{
f_a();
}
#include "aip-cpp-sdk-0.4.0/speech.h"
void f_a()
{
aip::f_s();
}
$$=>$$
#include "aip-cpp-sdk-0.4.0/base/base.h"
namespace aip {
void f_s() {
aip::f_i();
}
}
void f_a()
{
aip::f_s();
}
$$=>$$
#include "aip-cpp-sdk-0.4.0/base/base64.h"
namespace aip {
void f_s() {
aip::f_i();
}
}
void f_a()
{
aip::f_s();
}
$$=>$$
include <iostream>
namespace aip { void f() { std::cout << "hello"; }
namespace aip {
void f_s() {
aip::f_i();
}
}
void f_a()
{
aip::f_s();
}
#include "a.h"
int main()
{
f_a();
}
$$=>$$
bool f_a();
int main()
{
f_a();
}
可見這個(gè)版本展開到最後沒有違背ODR的部分.
#include "a.h"
void f_a()
{
aip::f_s();
}
$$=>$$
#include "aip-cpp-sdk-0.4.0/base/base64.h"
void f_a();
void f_a()
{
aip::f_s();
}
接下來的步驟幾乎於版本一相同了, 直接貼最後結(jié)果:
void f_a();
include <iostream>
namespace aip { void f() { std::cout << "hello"; }
namespace aip {
void f_s() {
aip::f_i();
}
}
void f_a()
{
aip::f_s();
}
#include "a.h"
int main()
{
f_a();
}
只要展開a.h, 上面也有此步驟, 所以直接給結(jié)果:
void f_a();
include <iostream>
namespace aip { void f() { std::cout << "hello"; }
namespace aip {
void f_s() {
aip::f_i();
}
}
int main()
{
f_a();
}
好, 其實(shí)我們很容易發(fā)現(xiàn), 版本二的兩個(gè)翻譯單元都會(huì)包含aip::f_i()和aip::f_i的definition, 再結(jié)合原理部分的最後一段, 這明顯違反了ODR`, 所以是錯(cuò)誤的
兩位老大已經(jīng)給出了方案了, 要麼你自己的頭文件和源文件小心處理, 理清依賴關(guān)係, 要麼你就給庫函數(shù)加上inline, 屏蔽ODR檢查.
@藤壺女御_ 老大誤以爲(wèi)窩是臺(tái)灣人, 真是夭壽啦:P 畢竟窩吃不起茶葉蛋, 用著799的碎屏紅米(碎1年了), 穿著迪卡農(nóng)的鞋子, 筆記本的價(jià)格也只有1700RMB(3年了已經(jīng)), 只是最近喜歡上繁體字了, 所以窩用byvoid老大的Open CC提供簡體版:
瀉藥, @李毅 老大已經(jīng)點(diǎn)名你出錯(cuò)的地方了, @felix 老大也指出是ODR的問題, 看來窩除了能在上面說下原理沒什么做了, 哈哈. 不過既然兩位老大都沒有將原理和你的庫結(jié)合, 那么這個(gè)微小工作就由窩踩在兩位老大的肩膀上來完成吧.
窩給你結(jié)合標(biāo)準(zhǔn)文檔從頭梳理一些c++里面必須知道的入門常識(shí)性概念, 這些概念可能你會(huì)覺得過有點(diǎn)多余, 但是這些是每一個(gè)寫c++的必須知道的基礎(chǔ). 并且窩在這里會(huì)剔除些標(biāo)準(zhǔn)里的wording, 只會(huì)提及此處需要用到的概念.
以下引用皆出自N4741, 歌詞大意可以理解為Informally的簡略版解釋(可能有錯(cuò)誤的私貨, 如有老大看出, 望告知)
這在標(biāo)準(zhǔn)里面沒有, 只是c++程序員為了實(shí)現(xiàn)ODR的一個(gè)慣用法罷了, 通過conditional inclusion在頭文件里面定義大部分時(shí)間都可以用#pragma once 來代替, 不過窩有一次被一個(gè)大佬警告過其可移植性, 但是窩查了下發(fā)現(xiàn)msvc, clang, gcc, icc, xl都zici呀...可能是有標(biāo)準(zhǔn)潔癖吧.
The text of the program is kept in units called source files in this document. A source file together with all the headers (20.5.1.2) and source files included (19.2) via the preprocessing directive#include, less any source lines skipped by any of the conditional inclusion (19.1) preprocessing directives, is called a translation unit.
歌詞大意: 每一個(gè)源文件(.cpp/.cc/.cxx等)在使用了include guard后展開頭文件(.hh/.hpp/.h等)(即復(fù)制頭文件所有內(nèi)容進(jìn)源文件)
Each entity declared by a declaration is also defined by that declaration, unless..........
歌詞大意, $$\mathsf{definitions} \subset \mathsf{declarations}$$, 這就是這兩者的關(guān)系, 不過窩在某個(gè)dlang的群里發(fā)現(xiàn)很多寫了c++多年的選手依然不知道這一點(diǎn), 這是很致命的, 比如不熟悉其區(qū)別可能就會(huì)產(chǎn)生窩這樣的困惑:https://stackoverflow.com/que... 還可能有其它危害, 下文也會(huì)提及.
在n4741中, ODR分為了12大點(diǎn), 以后還可能擴(kuò)充或修改, 前些日子在so上看到一個(gè)語言律師在odr-use上發(fā)現(xiàn)了自相矛盾的地方. 不過這里只需要說下兩點(diǎn)就行了.
1) No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, or template.
本來不想說odr-used的, 但是發(fā)現(xiàn)還是脫不開它.
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (7.1) to x yields a constant expression (8.6) that does not invoke any non-trivial functions and, if x is an object,ex is an element of the set of potential results of an expressione, where either the lvalue-to-rvalue conversion (7.1) is applied toe, or e is a discarded-value expression (8.2).
歌詞大意: 先要了解potentially-evaluated expression是什么, 花個(gè)1分鐘看下這個(gè)帖子: https://stackoverflow.com/que... 好, 當(dāng)ex滿足potentially-evaluated expression的性質(zhì)時(shí), 除非做了左值->右值的轉(zhuǎn)換(如x作為返回值, 但是是按值返回的), 亦或是x本身不是object, 比如x可以是引用(另一種情況這不解釋了, 不然牽扯的概念就太多了). 好, 說這些可能會(huì)有點(diǎn)混, 其實(shí), 你需要知道的是odr-use的意思就是如字面所說--需要definition的存在, 而不僅僅是declaration(現(xiàn)在你應(yīng)該明白為什么窩在一開始就要區(qū)分definition和declaration及其子集關(guān)系了吧).
繼續(xù)回到One definition rule的定義
10) Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement (9.4.1); no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 15.1, 15.4 and 15.8). An inline function or variable shall be defined in every translation unit in which it is odr-used outside of a discarded statement.
歌詞大意: 對(duì)于函數(shù)或者變量, 在整個(gè)程序中也要遵循odr原則, 但是很明顯頭文件會(huì)被很多源文件包含, 該怎么辦呢? inline用來開洞解決這個(gè)問題, 加了inline, 對(duì)函數(shù)的odr檢查就被無視了(當(dāng)然, 很多時(shí)候會(huì)自動(dòng)inline, 比如類內(nèi)定義, friend等).
窩不知道你自己的程式結(jié)構(gòu), 那么我就沿用@李毅 老大給出的文件組織了:
├── a.cpp
├── a.h
├── aip-cpp-sdk-0.4.0
│ ├── base
│ │ ├── base64.h
│ │ ├── base.h
│ │ ├── http.h
│ │ └── utils.h
│ ├── face.h
│ ├── image_censor.h
│ ├── image_classify.h
│ ├── image_search.h
│ ├── kg.h
│ ├── nlp.h
│ ├── ocr.h
│ ├── README.md
│ └── speech.h
├── main.cpp
└── Makefile
很明顯, translation parsion之后我們由a.cpp和main.cpp兩個(gè)翻譯單元存在, 好, 我們通過展開這兩個(gè)翻譯單元來分析為什么你自己寫的(即版本二)會(huì)違背ODR, 而@李毅 老大給出的第一個(gè)版本就不會(huì). 為了進(jìn)一步簡化問題, 我們把#include <json/json.h>不予考慮, 規(guī)定base/base.h的內(nèi)容除去include guard僅有#include "base64.h一行, base/base64.h除去include guard有
include "iostream"
namespace aip { void f() { std::cout << "hello"; }
speech.h除去include guard有
#include "base/base.h"
namespace aip {
void f_s() {
aip::f_i();
}
}
main.c有:
#include "a.h"
int main()
{
f_a();
}
#include "aip-cpp-sdk-0.4.0/speech.h"
void f_a()
{
aip::f_s();
}
$$=>$$
#include "aip-cpp-sdk-0.4.0/base/base.h"
namespace aip {
void f_s() {
aip::f_i();
}
}
void f_a()
{
aip::f_s();
}
$$=>$$
#include "aip-cpp-sdk-0.4.0/base/base64.h"
namespace aip {
void f_s() {
aip::f_i();
}
}
void f_a()
{
aip::f_s();
}
$$=>$$
include <iostream>
namespace aip { void f() { std::cout << "hello"; }
namespace aip {
void f_s() {
aip::f_i();
}
}
void f_a()
{
aip::f_s();
}
#include "a.h"
int main()
{
f_a();
}
$$=>$$
bool f_a();
int main()
{
f_a();
}
可見這個(gè)版本展開到最后沒有違背ODR的部分.
#include "a.h"
void f_a()
{
aip::f_s();
}
$$=>$$
#include "aip-cpp-sdk-0.4.0/base/base64.h"
void f_a();
void f_a()
{
aip::f_s();
}
接下來的步驟幾乎于版本一相同了, 直接貼最后結(jié)果:
void f_a();
include <iostream>
namespace aip { void f() { std::cout << "hello"; }
namespace aip {
void f_s() {
aip::f_i();
}
}
void f_a()
{
aip::f_s();
}
#include "a.h"
int main()
{
f_a();
}
只要展開a.h, 上面也有此步驟, 所以直接給結(jié)果:
void f_a();
include <iostream>
namespace aip { void f() { std::cout << "hello"; }
namespace aip {
void f_s() {
aip::f_i();
}
}
int main()
{
f_a();
}
好, 其實(shí)我們很容易發(fā)現(xiàn), 版本二的兩個(gè)翻譯單元都會(huì)包含aip::f_i()和aip::f_i的definition, 再結(jié)合原理部分的最后一段, 這明顯違反了ODR`, 所以是錯(cuò)誤的
兩位老大已經(jīng)給出了方案了, 要么你自己的頭文件和源文件小心處理, 理清依賴關(guān)系, 要么你就給庫函數(shù)加上inline, 屏蔽ODR檢查.
@藤壺女御_ 老大誤以為窩是臺(tái)灣人, 真是夭壽啦:P 畢竟窩吃不起茶葉蛋, 用著799的碎屏紅米(碎1年了), 穿著迪卡農(nóng)的鞋子, 筆記本的價(jià)格也只有1700RMB(3年了已經(jīng)), 只是最近喜歡上繁體字了
看起來最可能是因?yàn)檫@個(gè)頭文件不是用來讓你包含的。
如何產(chǎn)生的?
這個(gè)鏈接錯(cuò)誤是由于違背了ODR。即在你的程序中,同一個(gè)非內(nèi)聯(lián)函數(shù)被多次定義。
解決方案:
當(dāng)然還有很多別的方案,就不一一列舉了。總之,你需要保證在所有參與編譯的源文件(更準(zhǔn)確的說是參與鏈接的目標(biāo)文件)中,這樣的頭文件(定義了非內(nèi)聯(lián)函數(shù)的頭文件)至多只被其中的一個(gè)包含。
PS:和命名空間完全沒關(guān)系。
北大青鳥APTECH成立于1999年。依托北京大學(xué)優(yōu)質(zhì)雄厚的教育資源和背景,秉承“教育改變生活”的發(fā)展理念,致力于培養(yǎng)中國IT技能型緊缺人才,是大數(shù)據(jù)專業(yè)的國家
達(dá)內(nèi)教育集團(tuán)成立于2002年,是一家由留學(xué)海歸創(chuàng)辦的高端職業(yè)教育培訓(xùn)機(jī)構(gòu),是中國一站式人才培養(yǎng)平臺(tái)、一站式人才輸送平臺(tái)。2014年4月3日在美國成功上市,融資1
北大課工場(chǎng)是北京大學(xué)校辦產(chǎn)業(yè)為響應(yīng)國家深化產(chǎn)教融合/校企合作的政策,積極推進(jìn)“中國制造2025”,實(shí)現(xiàn)中華民族偉大復(fù)興的升級(jí)產(chǎn)業(yè)鏈。利用北京大學(xué)優(yōu)質(zhì)教育資源及背
博為峰,中國職業(yè)人才培訓(xùn)領(lǐng)域的先行者
曾工作于聯(lián)想擔(dān)任系統(tǒng)開發(fā)工程師,曾在博彥科技股份有限公司擔(dān)任項(xiàng)目經(jīng)理從事移動(dòng)互聯(lián)網(wǎng)管理及研發(fā)工作,曾創(chuàng)辦藍(lán)懿科技有限責(zé)任公司從事總經(jīng)理職務(wù)負(fù)責(zé)iOS教學(xué)及管理工作。
浪潮集團(tuán)項(xiàng)目經(jīng)理。精通Java與.NET 技術(shù), 熟練的跨平臺(tái)面向?qū)ο箝_發(fā)經(jīng)驗(yàn),技術(shù)功底深厚。 授課風(fēng)格 授課風(fēng)格清新自然、條理清晰、主次分明、重點(diǎn)難點(diǎn)突出、引人入勝。
精通HTML5和CSS3;Javascript及主流js庫,具有快速界面開發(fā)的能力,對(duì)瀏覽器兼容性、前端性能優(yōu)化等有深入理解。精通網(wǎng)頁制作和網(wǎng)頁游戲開發(fā)。
具有10 年的Java 企業(yè)應(yīng)用開發(fā)經(jīng)驗(yàn)。曾經(jīng)歷任德國Software AG 技術(shù)顧問,美國Dachieve 系統(tǒng)架構(gòu)師,美國AngelEngineers Inc. 系統(tǒng)架構(gòu)師。