Giriş
One Definition Rule yani ODR, bir "şeyin" tek bir yerde tek bir defa tanımlı olmasını gerektirir. Bu "şey" sınıf, metod vs olabilir. Yani bir kod parçasıdır.
One Definition Rule yani ODR, bir "şeyin" tek bir yerde tek bir defa tanımlı olmasını gerektirir. Bu "şey" sınıf, metod vs olabilir. Yani bir kod parçasıdır.
ODR C++'ın gıcık konularından birisi. Bu yüzden ODR kullanımı, ihlalleri ile ilgili maddeleri bu yazıda toplamaya çalıştım.
ODR Ne İçin Lazımdır?
ODR olmasaydı geriye doğru referanslar çalışmazdı. Şu kod derlenmezdi.
1. Aynı değişkenin iki farklı yerde tanımlanması
Açıklaması şöyle.
Şu kod ODR kuralını ihlal ettiği için derlenmez
ODR derlemeye dahil olmayan kodlar için haliyle işlemez. Elimizde şöyle bir kod olsun
2.Aynı metodun iki farklı yerde vücut bulması
Açıklaması şöyle.
Örnek - iki inline metodu
inline metod iki farklı yerde tanımlanabilir ve ODR kuralını ihlal etmez. Eğer inline metod iki farklı yerde tanımlı ise ise aynı koda sahip olmaları beklenir. inline specifier yazısına bakabilirsiniz.
3.Aynı İki Template Metodun İki Farklı Yerde Vücut Bulması
Örnek
Elimizde a.cpp olsun
Örnek
Elimizde şöyle bir kod olsun
Açıklaması şöyle.
Açıklaması şöyle. Hiding veya shadowing deniliyor.
Anoymous Namespace yazısına bakabilirsiniz. Açıklaması şöyle
Şu kod aynı sınıfı unnamed isim alanı içinde iki farklı yerde tanımladığı için ODR kuralını ihlal eder.
pimpl için şöyle yapılmış olsun. Impl önce forward declare edilir. Daha sonra Foo.h içinde kullanılır.
Şu kod sanırım problemsiz. MyClass.h dosyasında şöyle yaparız.
Şu kod da hatalıdır.
Şu kod da hatalıdır.
// Does not compile
struct tree {
struct node *root;
};
struct node {
struct node *left;
struct node *right;
struct tree *owner;
};
ODR Varsa Ne Olur?
ODR ihlali varsa linker'ın nasıl çalışacağı belirsiz. Açıklaması şöyle.The linker assumes that the ODR has NOT been violated and is therefor free to use ANY of the instantiations ...
ODR İhlalleri
Şimdi bazı ODR ihlali durumlarına bakalım
Açıklaması şöyle.
Örnek3.2 One-definition rule4 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 (6.4.1); no diagnostic required.
Şu kod ODR kuralını ihlal ettiği için derlenmez
int x;
int x;
Şu kod ODR kuralını ihlal etmez.// a declaration with an extern storage class specifier and without an initializer
extern int x;
extern int x;
ÖrnekODR derlemeye dahil olmayan kodlar için haliyle işlemez. Elimizde şöyle bir kod olsun
#include <iostream>
extern int i; // Only declaration
int func()
{
if constexpr (true)
return 0;
else if (i)
return i;
else
return -1;
}
int main()
{
int ret = func();
std::cout<<"Ret : "<<ret<<std::endl;
}
constexpr yüzünden alttaki i'yi kullanan kod derlenmediği için ODR kuralı işlemez.2.Aynı metodun iki farklı yerde vücut bulması
Açıklaması şöyle.
"In the entire program, an object or non-inline function cannot have more than one definition"Aynı metodun iki kere vücut bulması ile iki kere tanımlanması (declare) farklı şeylerdir. Şu kod ODR kuralını ihlal etmez.
int add(int, int);
int add(int, int);
Örnek - normal + inline metod
file1.cpp şöyle olsunint func1 (void) { return 5; }
file2.cpp şöyle olsuninline int func1 (void) { return 5; }
Bu kod her iki metod aynı olmasına rağmen, file2.cpp içindeki inline olduğu için derlenir.Örnek - iki inline metodu
inline metod iki farklı yerde tanımlanabilir ve ODR kuralını ihlal etmez. Eğer inline metod iki farklı yerde tanımlı ise ise aynı koda sahip olmaları beklenir. inline specifier yazısına bakabilirsiniz.
3.Aynı İki Template Metodun İki Farklı Yerde Vücut Bulması
Örnek
Elimizde a.cpp olsun
#include <iostream>
double bar();
template <typename T>
T foobar(T t) {
return t;
}
int main() {
std::cout << "foobar called from b.cpp: " << bar() << '\n';
std::cout << "foobar called from a.cpp: " << foobar(1.) << '\n';
}
Elimizde b.cpp olsuntemplate <typename T>
T foobar(T t) {
return t + 1.;
}
double bar() {
return foobar(1.);
}
Bu kod derlense bile ODR kuralını ihlal eder. Yani undefined behavior (UB) verir.Örnek
Elimizde şöyle bir kod olsun
#pragma once
#include <iostream>
template <int>
void func() { std::cerr << "default impl\n"; } // normally no impl here
func metodunu farklı template parametreleri ile farklı bir dosyada daha gerçekleştirelim#include "func.h"
template <>
void func<1>()
{
std::cerr << "special 1\n";
}
template <>
void func<2>()
{
std::cerr << "special 2\n";
}
Ve ilk metodu çağıralım#include "func.h"
int main(void)
{
func<1>();
func<2>();
return 0;
}
Bu kod derlense bile ODR kuralını ihlal eder. Yani undefined behavior (UB) verir. Optimizasyon olmadan derleyip çalıştırırsak çıktı olarak şunu alırızspecial 1
special 2
Optimizasyon ile derleyip çalıştırırsak çıktı olarak şunu alırızdefault impl
default impl
ÇözümAçıklaması şöyle.
The solution to this is to forward declare the specializations in your header:Şöyle yaparız.
template<> void func<1>();
template<> void func<2>();
4.Aynı metodun iki farklı yerde iki farklı isim alanı içinde vücut bulmasıAçıklaması şöyle. Hiding veya shadowing deniliyor.
Şöyle yaparız.A name can be hidden by an explicit declaration of that same name in a nested declarative region or derived class
#include <cmath>
double log(double) {return 1.0;}
int main() {
log(1.0);
}
5. Aynı sınıfın iki farklı yerde Anonymous/Unnamed isim alanı içinde sınıf tanımlanmasıAnoymous Namespace yazısına bakabilirsiniz. Açıklaması şöyle
"Use of unnamed namespaces in header files can easily cause violations of the C++ One Definition Rule (ODR)."Örnek
Şu kod aynı sınıfı unnamed isim alanı içinde iki farklı yerde tanımladığı için ODR kuralını ihlal eder.
//A.cpp
class X
{
};
//B.cpp
class X
{
int i;
};
Örnekpimpl için şöyle yapılmış olsun. Impl önce forward declare edilir. Daha sonra Foo.h içinde kullanılır.
namespace { struct Impl; }
class Foo
{
public:
Foo();
~Foo();
void Bar(int n);
/* ... */
private:
std::unique_ptr<Impl> _impl;
};
Foo.h'ı dahil eden her sınıf şu problemle karşılaşırIf this occurs in a header file that is used by multiple translation units, then I think it is an ODR violation. Each anonymous namespace is different in each translation unit, therefore class Foo will not have a consistent definition across all translation units.Doğrusu Impl'in Foo içinde tanımlanmasıydı. Şöyle yaparız.
class Foo
{
struct Impl;
public:
...
};
Tam bir PImpl .h dosyası şöyledir.// header file:
class Cat {
private:
class CatImpl; // Not defined here
CatImpl *cat_; // Handle
public:
Cat(); // Constructor
~Cat(); // Destructor
// Other operations...
Purr();
};
Tam bir PImpl cpp dosyası şöyledir.// CPP file:
#include "cat.h"
class Cat::CatImpl {
Purr();
... // The actual implementation can be anything
};
Cat::Cat() {
cat_ = new CatImpl;
}
Cat::~Cat() {
delete cat_;
}
Cat::Purr(){ cat_->Purr(); }
CatImpl::Purr(){
printf("purrrrrr");
}
ÖrnekŞu kod sanırım problemsiz. MyClass.h dosyasında şöyle yaparız.
#if !defined(MYCLASS_PUBLIC_H_)
#define MYCLASS_PUBLIC_H_
struct MyClassImpl;
class MyClass {
MyClassImpl * pImpl;
public:
void SomeOperation();
};
#endif
MyClassImpl.h dosyasında şöyle yaparız.#if !defined(MYCLASS_IMPL_H_)
#define MYCLASS_IMPL_H_
#include <private_type.h>
#include "MyClass.h"
struct MyClassImpl
{
void Operation();
private:
SomePrivateType member;
};
#endif
MyClassImpl.cpp dosyasında şöyle yaparız.#include "MyClassImpl.h"
void MyClass::SomeOperation()
{
pImpl->Operation();
}
void MyClassImpl::Operation()
{
// do something with 'member'
}
ÖrnekŞu kod da hatalıdır.
namespace {
double const pi = 3.14159;
}
inline double twoPiR( double r ) { return 2.0 * pi * r; }
ÖrnekŞu kod da hatalıdır.
namespace {
int x;
}
namespace{
int x;
}
Hiç yorum yok:
Yorum Gönder