在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 問答/C++/ c++ 聲明定義都在頭文件中怎么include?

c++ 聲明定義都在頭文件中怎么include?

下了一些百度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.如何解決?

回答
編輯回答
憶當(dāng)年

使用你提供的 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?...

2018年2月28日 22:46
編輯回答
久礙你

瀉藥, @李毅 老大已經(jīng)點(diǎn)名你出錯(cuò)的地方了, @felix 老大也指出是ODR的問題, 看來窩除了能在上面說下原理沒什麼做了, 哈哈. 不過既然兩位老大都沒有將原理和你的庫結(jié)合, 那麼這個(gè)微小工作就由窩踩在兩位老大的肩膀上來完成吧.

科普基礎(chǔ)知識(shí)

窩給你結(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ò)誤的私貨, 如有老大看出, 望告知)

Include guard

這在標(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)潔癖吧.

Translation unit

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)源文件)

Declarations and definitions

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ì)提及.

One Definition Rule(ODR)

在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-use

本來不想說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等).


根據(jù)原理剖析錯(cuò)因.

窩不知道你自己的程式結(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.cppmain.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();
}

版本一

a.cpp(稍作簡化)

#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();
}

main.cpp

#include "a.h"

int main()
{
    f_a();
}

$$=>$$

bool f_a();
int main()
{
    f_a();
}

小結(jié)

可見這個(gè)版本展開到最後沒有違背ODR的部分.

版本二

a.cpp

#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();
}

main.cpp

#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();
}

小結(jié)

好, 其實(shí)我們很容易發(fā)現(xiàn), 版本二的兩個(gè)翻譯單元都會(huì)包含aip::f_i()aip::f_i的definition, 再結(jié)合原理部分的最後一段, 這明顯違反了ODR`, 所以是錯(cuò)誤的

解決方案

兩位老大已經(jīng)給出了方案了, 要麼你自己的頭文件和源文件小心處理, 理清依賴關(guān)係, 要麼你就給庫函數(shù)加上inline, 屏蔽ODR檢查.

誰背鍋?

  1. c++. 沒有module, 只能依靠這種落伍的include guard和inline來解決這種包含和符號(hào)檢查, 很容易會(huì)混亂心智
  2. c. 歷史殘留
  3. 庫的作者, 窩懷疑他們自己都沒有用過自己寫的這個(gè)庫.

@藤壺女御_ 老大誤以爲(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è)微小工作就由窩踩在兩位老大的肩膀上來完成吧.

科普基礎(chǔ)知識(shí)

窩給你結(jié)合標(biāo)準(zhǔn)文檔從頭梳理一些c++里面必須知道的入門常識(shí)性概念, 這些概念可能你會(huì)覺得過有點(diǎn)多余, 但是這些是每一個(gè)寫c++的必須知道的基礎(chǔ). 并且窩在這里會(huì)剔除些標(biāo)準(zhǔn)里的wording, 只會(huì)提及此處需要用到的概念.

以下引用皆出自N4741, 歌詞大意可以理解為Informally的簡略版解釋(可能有錯(cuò)誤的私貨, 如有老大看出, 望告知)

Include guard

這在標(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)潔癖吧.

Translation unit

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)源文件)

Declarations and definitions

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ì)提及.

One Definition Rule(ODR)

在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-use

本來不想說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等).


根據(jù)原理剖析錯(cuò)因.

窩不知道你自己的程式結(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.cppmain.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();
}

版本一

a.cpp(稍作簡化)

#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();
}

main.cpp

#include "a.h"

int main()
{
    f_a();
}

$$=>$$

bool f_a();
int main()
{
    f_a();
}

小結(jié)

可見這個(gè)版本展開到最后沒有違背ODR的部分.

版本二

a.cpp

#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();
}

main.cpp

#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();
}

小結(jié)

好, 其實(shí)我們很容易發(fā)現(xiàn), 版本二的兩個(gè)翻譯單元都會(huì)包含aip::f_i()aip::f_i的definition, 再結(jié)合原理部分的最后一段, 這明顯違反了ODR`, 所以是錯(cuò)誤的

解決方案

兩位老大已經(jīng)給出了方案了, 要么你自己的頭文件和源文件小心處理, 理清依賴關(guān)系, 要么你就給庫函數(shù)加上inline, 屏蔽ODR檢查.

誰背鍋?

  1. c++. 沒有module, 只能依靠這種落伍的include guard和inline來解決這種包含和符號(hào)檢查, 很容易會(huì)混亂心智
  2. c. 歷史殘留
  3. 庫的作者, 窩懷疑他們自己都沒有用過自己寫的這個(gè)庫.

@藤壺女御_ 老大誤以為窩是臺(tái)灣人, 真是夭壽啦:P 畢竟窩吃不起茶葉蛋, 用著799碎屏紅米(碎1年了), 穿著迪卡農(nóng)的鞋子, 筆記本的價(jià)格也只有1700RMB(3年了已經(jīng)), 只是最近喜歡上繁體字了

參考: http://eel.is/c++draft/#tab

2018年2月4日 00:45
編輯回答
萢萢糖

看起來最可能是因?yàn)檫@個(gè)頭文件不是用來讓你包含的。

如何產(chǎn)生的?
這個(gè)鏈接錯(cuò)誤是由于違背了ODR。即在你的程序中,同一個(gè)非內(nèi)聯(lián)函數(shù)被多次定義。

解決方案:

  1. 不要包含這樣的頭文件。
  2. 在所有這樣的函數(shù)前加上inline。
  3. 只在一個(gè)源文件中包含這樣的頭文件。

當(dāng)然還有很多別的方案,就不一一列舉了。總之,你需要保證在所有參與編譯的源文件(更準(zhǔn)確的說是參與鏈接的目標(biāo)文件)中,這樣的頭文件(定義了非內(nèi)聯(lián)函數(shù)的頭文件)至多只被其中的一個(gè)包含。

PS:和命名空間完全沒關(guān)系。

2018年6月15日 00:54