C ++ ' nihayet ' blokları destekliyor mu?
Nedir RAII deyim ?
C ++ 'ın RAII deyimi ile C #' s 'using' ifadesi arasındaki fark nedir?
C ++ ' nihayet ' blokları destekliyor mu?
Nedir RAII deyim ?
C ++ 'ın RAII deyimi ile C #' s 'using' ifadesi arasındaki fark nedir?
Yanıtlar:
Hayır, C ++ 'nihayet' blokları desteklemez. Bunun nedeni, C ++'ın RAII'yi desteklemesidir: "Kaynak Edinimi Başlatmadır" - gerçekten kullanışlı bir kavram için kötü bir isim † .
Fikir, bir nesnenin yıkıcısının kaynakları boşaltmaktan sorumlu olmasıdır. Nesne otomatik depolama süresine sahip olduğunda, nesnenin yıkıcısı, oluşturulduğu blok çıktığında (bu blok bir istisna varlığında çıkılsa bile) çağrılır. İşte Bjarne Stroustrup'un konuyla ilgili açıklaması .
RAII için yaygın bir kullanım, bir muteksi kilitlemektir:
// A class with implements RAII
class lock
{
mutex &m_;
public:
lock(mutex &m)
: m_(m)
{
m.acquire();
}
~lock()
{
m_.release();
}
};
// A class which uses 'mutex' and 'lock' objects
class foo
{
mutex mutex_; // mutex for locking 'foo' object
public:
void bar()
{
lock scopeLock(mutex_); // lock object.
foobar(); // an operation which may throw an exception
// scopeLock will be destructed even if an exception
// occurs, which will release the mutex and allow
// other functions to lock the object and run.
}
};
RAII ayrıca nesneleri diğer sınıfların üyeleri olarak kullanmayı da kolaylaştırır. Sahip olan sınıf 'yok edildiğinde, RAII sınıfı tarafından yönetilen kaynak serbest bırakılır, çünkü RAII tarafından yönetilen sınıfın yıkıcısı sonuç olarak çağrılır. Bu, kaynakları yöneten bir sınıftaki tüm üyeler için RAII kullandığınızda, üye kaynak yaşamlarını manuel olarak yönetmesi gerekmediği için sahip sınıfı için çok basit, belki de varsayılan bir yıkıcıyı kullanarak kurtulabilirsiniz. . ( Bunu işaret ettiği için Mike B'ye teşekkürler .)
C # veya VB.NET içeren familliarlar için, RAII'nin IDisposable ve 'using' ifadelerini kullanarak .NET deterministik yıkımına benzer olduğunu fark edebilirsiniz . Aslında, iki yöntem birbirine çok benzer. Temel fark, RAII'nin bellek dahil olmak üzere her türlü kaynağı kararlı bir şekilde serbest bırakmasıdır. .NET'te IDisposable (.NET dili C ++ / CLI bile) uygulandığında, bellek dışında kaynaklar deterministik olarak serbest bırakılır. .NET'te, bellek deterministik olarak serbest bırakılmaz; bellek yalnızca çöp toplama döngüleri sırasında serbest bırakılır.
† Bazı insanlar "Yıkımın Kaynaktan Ayrılması" nın RAII deyimi için daha doğru bir isim olduğuna inanmaktadır.
C ++ 'da nihayet RAII nedeniyle gerekli DEĞİLDİR .
RAII, istisna güvenliği sorumluluğunu nesnenin kullanıcısından nesnenin tasarımcısına (ve uygulayıcısına) taşır. O zaman sadece bir kez (tasarım / uygulamada) istisna güvenliği doğru almak gerekir gibi bu doğru yer olduğunu iddia ediyorum. Son olarak, bir nesneyi her kullandığınızda istisna güvenliğini düzeltmeniz gerekir.
Ayrıca IMO kodu daha temiz görünüyor (aşağıya bakınız).
Misal:
Bir veritabanı nesnesi. DB bağlantısının kullanıldığından emin olmak için açılmalı ve kapatılmalıdır. RAII kullanarak bu yapıcı / yıkıcı içinde yapılabilir.
void someFunc()
{
DB db("DBDesciptionString");
// Use the db object.
} // db goes out of scope and destructor closes the connection.
// This happens even in the presence of exceptions.
RAII kullanımı, bir DB nesnesinin doğru kullanımını çok kolaylaştırır. DB nesnesi, onu nasıl denediğimiz ve kötüye kullandığımız önemli değil, bir yıkıcı kullanarak kendini doğru şekilde kapatacaktır.
void someFunc()
{
DB db = new DB("DBDesciptionString");
try
{
// Use the db object.
}
finally
{
// Can not rely on finaliser.
// So we must explicitly close the connection.
try
{
db.close();
}
catch(Throwable e)
{
/* Ignore */
// Make sure not to throw exception if one is already propagating.
}
}
}
Son olarak kullanıldığında, nesnenin doğru kullanımı, nesnenin kullanıcısına devredilir. yani DB bağlantısını açıkça kapatmak nesne kullanıcısının sorumluluğundadır. Şimdi bunun sonlandırıcıda yapılabileceğini iddia edebilirsiniz, ancak kaynakların sınırlı kullanılabilirliği veya diğer kısıtlamaları olabilir ve bu nedenle genellikle nesnenin serbest bırakılmasını kontrol etmek ve çöp toplayıcının deterministik olmayan davranışına güvenmek istemezsiniz.
Ayrıca bu basit bir örnek.
Serbest bırakılması gereken birden fazla kaynağınız olduğunda kod karmaşıklaşabilir.
Daha ayrıntılı bir analiz burada bulunabilir: http://accu.org/index.php/journals/236
// Make sure not to throw exception if one is already propagating.
C ++ yıkıcılarının bu nedenle istisnalar atmaması önemlidir.
RAII genellikle daha iyidir, ancak C ++ ' da nihayet anlambilime kolayca sahip olabilirsiniz . Az miktarda kod kullanma.
Ayrıca, C ++ Temel Yönergeleri nihayet verir.
İşte bağlantısıdır GSL Microsoft uygulanması için ve bir bağlantı Martin Moene uygulanması
Bjarne Stroustrup birçok kez GSL'de olan her şeyin sonunda standartta olması gerektiğini söyledi. Bu yüzden sonunda kullanmak için geleceğe dönük bir yol olmalı .
İsterseniz kolayca uygulayabilirsiniz, okumaya devam edin.
C ++ 11 RAII ve lambdas, nihayet bir genel yapmaya izin verir:
namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
FinalAction(F f) : clean_{f} {}
~FinalAction() { if(enabled_) clean_(); }
void disable() { enabled_ = false; };
private:
F clean_;
bool enabled_{true}; }; }
template <typename F>
detail::FinalAction<F> finally(F f) {
return detail::FinalAction<F>(f); }
kullanım örneği:
#include <iostream>
int main() {
int* a = new int;
auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; });
std::cout << "doing something ...\n"; }
çıktı:
doing something...
leaving the block, deleting a!
Şahsen bu birkaç kez bir C ++ programında POSIX dosya tanımlayıcıyı kapatmak için kullandım.
Kaynakları yöneten ve böylece her türlü sızıntıyı önleyen gerçek bir sınıfa sahip olmak genellikle daha iyidir, ancak bu nihayet bir sınıfın aşırıya kaçmasına benzediği durumlarda yararlıdır.
Ayrıca, nihayet diğer dillerden daha çok hoşuma gidiyor çünkü doğal olarak kullanılırsa kapanış kodunu açılış kodunun yakınına yazıyorsunuz (benim örneğimde yeni ve sil ) ve imha, C ++ 'da her zamanki gibi LIFO düzeninde inşaatı takip ediyor. Tek dezavantajı, gerçekten kullanmadığınız bir otomatik değişken elde etmeniz ve lambda sözdizimini biraz gürültülü hale getirmesidir (örneğimde dördüncü satırda sadece son olarak kelime ve sağdaki {} -block anlamlı, dinlenme aslında gürültü).
Başka bir örnek:
[...]
auto precision = std::cout.precision();
auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
std::cout << std::setprecision(3);
Devre dışı bırakma üyesi, yalnızca başarısızlık durumunda son aranması gerektiğinde yararlıdır . Örneğin, bir nesneyi üç farklı kapsayıcıya kopyalamanız gerekir, son olarak her kopyayı geri alacak ve tüm kopyalar başarılı olduktan sonra devre dışı bırakacak şekilde ayarlayabilirsiniz . Bunu yaparsanız, yıkım atılamazsa, güçlü garantiyi garanti edersiniz.
örneği devre dışı bırak :
//strong guarantee
void copy_to_all(BIGobj const& a) {
first_.push_back(a);
auto undo_first_push = finally([first_&] { first_.pop_back(); });
second_.push_back(a);
auto undo_second_push = finally([second_&] { second_.pop_back(); });
third_.push_back(a);
//no necessary, put just to make easier to add containers in the future
auto undo_third_push = finally([third_&] { third_.pop_back(); });
undo_first_push.disable();
undo_second_push.disable();
undo_third_push.disable(); }
C ++ 11 kullanamıyorsanız, son olarak sahip olabilirsiniz , ancak kod biraz daha uzun sarılır. Sadece bir yapıcı ve yıkıcı ile bir yapı tanımlayın, yapıcı ihtiyaç duyduğunuz her şeye referanslar alır ve yıkıcı ihtiyacınız olan eylemleri yapar. Temel olarak lambda'nın yaptığı budur.
#include <iostream>
int main() {
int* a = new int;
struct Delete_a_t {
Delete_a_t(int* p) : p_(p) {}
~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
int* p_;
} delete_a(a);
std::cout << "doing something ...\n"; }
FinalAction
Temel olarak popüler ScopeGuard
deyimle aynı olduğunu unutmayın , sadece farklı bir adla.
Yığın tabanlı nesnelerle temizlemeyi kolaylaştırmanın ötesinde, RAII de yararlıdır, çünkü nesne 'sınıfın bir üyesi olduğunda aynı' otomatik 'temizleme gerçekleşir. Sahip olan sınıf yok edildiğinde, RAII sınıfı tarafından yönetilen kaynak temizlenir, çünkü bu sınıfın dtor'u sonuç olarak çağrılır.
Bu, RAII nirvana'ya ulaştığınızda ve bir sınıftaki tüm üyeler RAII (akıllı işaretçiler gibi) kullandığında, sahibinin sınıfı için çok basit (belki de varsayılan) bir dtor ile manuel olarak yönetilmesi gerekmediği anlamına gelir. üye kaynak ömürleri.
neden çöp toplayıcı tarafından kaynakların otomatik olarak dağıtılmasına rağmen yönetilen diller bile nihayet bir engelleme sağlıyor?
Aslında, Çöp toplayıcılarına dayalı diller "nihayet" daha fazlasına ihtiyaç duyar. Bir çöp toplayıcı nesnelerinizi zamanında yok etmez, bu nedenle bellekle ilgili olmayan sorunları doğru bir şekilde temizlemek için güvenilemez.
Dinamik olarak ayrılmış veriler açısından, çoğu kişi akıllı işaretçiler kullanmanız gerektiğini savunur.
Ancak...
RAII, istisna güvenliği sorumluluğunu nesnenin kullanıcısından tasarımcıya taşır
Ne yazık ki bu kendi çöküşü. Eski C programlama alışkanlıkları çok ölüyor. C veya çok C stilinde yazılmış bir kitaplık kullandığınızda, RAII kullanılmaz. Tüm API ön ucunu yeniden yazmadan kısa bir süre sonra, bununla çalışmak zorundasınız. Sonra "nihayet" eksikliği gerçekten ısırır.
CleanupFailedException
. RAII kullanarak böyle bir sonuç elde etmenin makul bir yolu var mı?
SomeObject.DoSomething()
yöntemi çağıracağı ve (1) başarılı, (2) yan etkisi olmadan başarısız olduğu , (3) arayanın başa çıkmaya hazır olduğu yan etkilerle başarısız olup olmadığını bilmek istediği birçok durum vardır. veya (4) arayanın baş edemediği yan etkilerle başarısız oldu. Sadece arayan, hangi durumlarla başa çıkabileceğini ve başa çıkamayacağını bilecektir; Arayanın ihtiyacı olan şey, durumun ne olduğunu bilmenin bir yoludur. Bir istisna hakkında en önemli bilgileri sağlamak için standart bir mekanizma olmaması çok kötü.
C ++ 11 lambda fonksiyonlarını kullanan bir başka "son olarak" blok emülasyonu
template <typename TCode, typename TFinallyCode>
inline void with_finally(const TCode &code, const TFinallyCode &finally_code)
{
try
{
code();
}
catch (...)
{
try
{
finally_code();
}
catch (...) // Maybe stupid check that finally_code mustn't throw.
{
std::terminate();
}
throw;
}
finally_code();
}
Umarım derleyici yukarıdaki kodu optimize eder.
Şimdi böyle bir kod yazabiliriz:
with_finally(
[&]()
{
try
{
// Doing some stuff that may throw an exception
}
catch (const exception1 &)
{
// Handling first class of exceptions
}
catch (const exception2 &)
{
// Handling another class of exceptions
}
// Some classes of exceptions can be still unhandled
},
[&]() // finally
{
// This code will be executed in all three cases:
// 1) exception was not thrown at all
// 2) exception was handled by one of the "catch" blocks above
// 3) exception was not handled by any of the "catch" block above
}
);
İsterseniz bu deyimi "try - nihayet" makrolarına sarabilirsiniz:
// Please never throw exception below. It is needed to avoid a compilation error
// in the case when we use "begin_try ... finally" without any "catch" block.
class never_thrown_exception {};
#define begin_try with_finally([&](){ try
#define finally catch(never_thrown_exception){throw;} },[&]()
#define end_try ) // sorry for "pascalish" style :(
Şimdi "nihayet" blok C ++ 11'de kullanılabilir:
begin_try
{
// A code that may throw
}
catch (const some_exception &)
{
// Handling some exceptions
}
finally
{
// A code that is always executed
}
end_try; // Sorry again for this ugly thing
Şahsen ben "nihayet" deyimin "makro" sürümünü sevmiyorum ve bu durumda sözdizimi daha hantal olsa bile saf "with_finally" fonksiyonu kullanmayı tercih ederim.
Yukarıdaki kodu buradan test edebilirsiniz: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
Kodunuzda nihayet bir bloğa ihtiyacınız varsa , kapsamlı korumalar veya ON_FINALLY / ON_EXCEPTION makroları muhtemelen ihtiyaçlarınıza daha iyi uyacaktır.
İşte ON_FINALLY / ON_EXCEPTION kısa kullanım örneği:
void function(std::vector<const char*> &vector)
{
int *arr1 = (int*)malloc(800*sizeof(int));
if (!arr1) { throw "cannot malloc arr1"; }
ON_FINALLY({ free(arr1); });
int *arr2 = (int*)malloc(900*sizeof(int));
if (!arr2) { throw "cannot malloc arr2"; }
ON_FINALLY({ free(arr2); });
vector.push_back("good");
ON_EXCEPTION({ vector.pop_back(); });
...
Böyle eski bir iş parçacığını kazdığımız için özür dilerim, ancak aşağıdaki mantıkta büyük bir hata var:
RAII, istisna güvenliği sorumluluğunu nesnenin kullanıcısından nesnenin tasarımcısına (ve uygulayıcısına) taşır. O zaman sadece bir kez (tasarım / uygulamada) istisna güvenliği doğru almak gerekir gibi bu doğru yer olduğunu iddia ediyorum. Son olarak, bir nesneyi her kullandığınızda istisna güvenliğini düzeltmeniz gerekir.
Daha sık olmamakla birlikte, dinamik olarak tahsis edilmiş nesneler, dinamik nesne sayıları vb. Şimdi, bu egzotik bir senaryo değil, çok yaygın. Bu durumda, aşağıdaki gibi şeyler yazmak istersiniz:
void DoStuff(vector<string> input)
{
list<Foo*> myList;
try
{
for (int i = 0; i < input.size(); ++i)
{
Foo* tmp = new Foo(input[i]);
if (!tmp)
throw;
myList.push_back(tmp);
}
DoSomeStuff(myList);
}
finally
{
while (!myList.empty())
{
delete myList.back();
myList.pop_back();
}
}
}
Tabii ki kapsam dışına çıkarken listenin kendisi yok edilecek, ancak bu oluşturduğunuz geçici nesneleri temizlemeyecektir.
Bunun yerine, çirkin rotaya gitmelisiniz:
void DoStuff(vector<string> input)
{
list<Foo*> myList;
try
{
for (int i = 0; i < input.size(); ++i)
{
Foo* tmp = new Foo(input[i]);
if (!tmp)
throw;
myList.push_back(tmp);
}
DoSomeStuff(myList);
}
catch(...)
{
}
while (!myList.empty())
{
delete myList.back();
myList.pop_back();
}
}
Ayrıca: yönetilen kanallar bile, çöp toplayıcı tarafından otomatik olarak yeniden yerleştirilen kaynaklara rağmen nihayet bir engelleme sağlıyor?
İpucu: "nihayet" ile yapabileceğiniz daha çok bellek ayırmadan daha fazlasını yapabilirsiniz.
new
NULL döndürmez, bunun yerine bir istisna atar
std::shared_ptr
ve std::unique_ptr
doğrudan stdlib'i içerir.
FWIW, Microsoft Visual C ++, nihayet denemeyi destekliyor ve tarihsel olarak MFC uygulamalarında, aksi takdirde bir çökmeye neden olacak ciddi istisnaları yakalama yöntemi olarak kullanıldı. Örneğin;
int CMyApp::Run()
{
__try
{
int i = CWinApp::Run();
m_Exitok = MAGIC_EXIT_NO;
return i;
}
__finally
{
if (m_Exitok != MAGIC_EXIT_NO)
FaultHandler();
}
}
Bunu geçmişte çıkıştan önce açık dosyaların yedeklerini kaydetmek gibi şeyler yapmak için kullandım. Ancak bazı JIT hata ayıklama ayarları bu mekanizmayı bozacaktır.
Diğer cevaplarda belirtildiği gibi, C ++ finally
benzeri işlevselliği destekleyebilir . Muhtemelen standart dilin bir parçası olmaya en yakın olan bu işlevselliğin uygulanması, Bjarne Stoustrup ve Herb Sutter tarafından düzenlenen C ++ kullanımı için en iyi uygulamalar olan C ++ Temel Yönergeleri'ne eşlik eden işlevdir. Uygulamasının uygulanmasıfinally
, Kılavuzlar Destek Kitaplığı'nın (GSL) bir parçasıdır . Yönergeler boyunca, finally
eski stil arabirimleriyle uğraşırken bunun kullanılması önerilir ve ayrıca uygun bir kaynak tanıtıcısı yoksa temizlemeyi ifade etmek için bir final_action nesnesi kullanma başlıklı kendi kılavuzuna sahiptir .
Bu nedenle, sadece C ++ desteklemekle kalmaz finally
, birçok yaygın kullanım durumunda da kullanılması önerilir.
GSL uygulamasının örnek kullanımı şöyle görünecektir:
#include <gsl/gsl_util.h>
void example()
{
int handle = get_some_resource();
auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });
// Do a lot of stuff, return early and throw exceptions.
// clean_that_resource will always get called.
}
GSL uygulaması ve kullanımı Paolo.Bolzoni'nin cevabındaki ile çok benzer . Farklardan biri, tarafından oluşturulan nesnenin çağrıdan gsl::finally()
yoksun olmasıdır disable()
. Bu işlevselliğe ihtiyacınız varsa (örneğin, birleştirildikten sonra kaynağı döndürmek ve istisnalar olmayacak şekilde), Paolo'nun uygulamasını tercih edebilirsiniz. Aksi takdirde, GSL kullanmak, alacağınız standart özellikleri kullanmaya yakındır.
Gerçekten değil, ancak onları bir dereceye kadar taklit edebilirsiniz, örneğin:
int * array = new int[10000000];
try {
// Some code that can throw exceptions
// ...
throw std::exception();
// ...
} catch (...) {
// The finally-block (if an exception is thrown)
delete[] array;
// re-throw the exception.
throw;
}
// The finally-block (if no exception was thrown)
delete[] array;
Nihayet bloğun, orijinal istisna atılmadan önce bir istisna atabileceğini ve böylece orijinal istisnayı atabileceğini unutmayın. Bu, bir Java nihayet bloğundakiyle aynı davranıştır. Ayrıca, return
try & catch bloklarının içinde kullanamazsınız .
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
bloğun içinden istisnalar atmaya izin vermelidir .
Ben ile geldi finally
kullanılabilecek makro neredeyse gibi ¹ finally
Java anahtar kelime; std::exception_ptr
ve arkadaşları, lambda fonksiyonlarını kullanır ve std::promise
bu nedenle C++11
veya daha fazlasını gerektirir ; ayrıca clang tarafından da desteklenen bileşik ifade ifadesi GCC uzantısını kullanır.
UYARI : Bu cevabın önceki bir versiyonunda , kavramın çok daha fazla sınırlaması olan farklı bir uygulaması kullanılmıştır.
İlk olarak bir yardımcı sınıf tanımlayalım.
#include <future>
template <typename Fun>
class FinallyHelper {
template <typename T> struct TypeWrapper {};
using Return = typename std::result_of<Fun()>::type;
public:
FinallyHelper(Fun body) {
try {
execute(TypeWrapper<Return>(), body);
}
catch(...) {
m_promise.set_exception(std::current_exception());
}
}
Return get() {
return m_promise.get_future().get();
}
private:
template <typename T>
void execute(T, Fun body) {
m_promise.set_value(body());
}
void execute(TypeWrapper<void>, Fun body) {
body();
}
std::promise<Return> m_promise;
};
template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
return FinallyHelper<Fun>(body);
}
Bir de asıl makro var.
#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try
#define finally }); \
true; \
({return __finally_helper.get();})) \
/***/
Bu şekilde kullanılabilir:
void test() {
try_with_finally {
raise_exception();
}
catch(const my_exception1&) {
/*...*/
}
catch(const my_exception2&) {
/*...*/
}
finally {
clean_it_all_up();
}
}
Uygulamanın kullanımı std::promise
çok kolaylaşır, ancak muhtemelen sadece gerekli işlevselliklerin yeniden uygulanmasıyla önlenebilecek biraz fazla gereksiz yükü de beraberinde getirir std::promise
.
¹ CAVEAT: Java sürümü gibi çalışmayan birkaç şey var finally
. Kafamın üstünden:
break
içinden açıklamadatry
catch()
lambda işlevi içinde yaşadıkları için, bloktan ve bloklarının ;catch()
sonra bloktry
: bu bir C ++ gereksinimi;try
ve catch()'s
blokları içinde bir dönüş yoksa , finally
makro döndürmek isteyecek koda genişleyeceği için derleme başarısız olur void
. Bu, bir çeşit makroya sahip olarak geçersiz sayılabilir finally_noreturn
.Sonuçta, bu şeyleri kendim kullanıp kullanamayacağımı bilmiyorum, ama onunla oynamak eğlenceliydi. :)
catch(xxx) {}
başlangıcında imkansız bir blok koyarak kurtulabilirsiniz finally
, burada xxx sadece en az bir catch bloğuna sahip olmak için sahte bir türdür.
catch(...)
getirecek değil mi?
xxx
Asla kullanılmayacak özel bir ad alanında belirsiz bir tür oluşturun .
Bir akış açısından okumak daha kolay olduğunu düşündüğüm için C ++ 11 dilinin mükemmel kabul edilebilir bir parçası finally
olması gerektiğini düşündüğüm bir kullanım durumum var . Kullanım durumum, nullptr
tüm iş parçacıklarını kapatmak için çalışma sonunda bir sentinin gönderildiği bir tüketici / üretici iş parçacığı zinciridir .
C ++ destekliyorsa, kodunuzun şöyle görünmesini istersiniz:
extern Queue downstream, upstream;
int Example()
{
try
{
while(!ExitRequested())
{
X* x = upstream.pop();
if (!x) break;
x->doSomething();
downstream.push(x);
}
}
finally {
downstream.push(nullptr);
}
}
Döngü çıktıktan sonra gerçekleştiğinden, sonunda bildirinizi döngü başlangıcına koymanın daha mantıklı olduğunu düşünüyorum ... ama bu arzulu bir düşünce çünkü C ++ ile yapamayız. Sıra o Not downstream
Eğer sentinel içinde koyamazsınız yüzden, başka bir iş parçacığı bağlı olduğu push(nullptr)
yıkıcı downstream
bu noktada yok edilemez çünkü ... diğer iplik alana kadar hayatta kalmak gerekirnullptr
.
İşte aynı şeyi yapmak için lambda ile bir RAII sınıfının nasıl kullanılacağı aşağıda açıklanmıştır:
class Finally
{
public:
Finally(std::function<void(void)> callback) : callback_(callback)
{
}
~Finally()
{
callback_();
}
std::function<void(void)> callback_;
};
ve işte nasıl kullanıyorsunuz:
extern Queue downstream, upstream;
int Example()
{
Finally atEnd([](){
downstream.push(nullptr);
});
while(!ExitRequested())
{
X* x = upstream.pop();
if (!x) break;
x->doSomething();
downstream.push(x);
}
}
Birçok kişinin belirttiği gibi, çözüm sonunda blokları önlemek için C ++ 11 özelliklerini kullanmaktır. Özelliklerden biri unique_ptr
.
İşte Mephane'nin yanıtı RAII kalıpları kullanılarak yazılmıştır.
#include <vector>
#include <memory>
#include <list>
using namespace std;
class Foo
{
...
};
void DoStuff(vector<string> input)
{
list<unique_ptr<Foo> > myList;
for (int i = 0; i < input.size(); ++i)
{
myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
}
DoSomeStuff(myList);
}
C ++ Standart Kitaplık kapsayıcıları ile unique_ptr kullanmaya bazı girişler burada
Bir alternatif sunmak istiyorum.
Sonunda bloğun her zaman çağrılmasını istiyorsanız, sadece son yakalama bloğundan sonra koyun (muhtemelen catch( ... )
bilinen istisnaları yakalamak olmalıdır )
try{
// something that might throw exception
} catch( ... ){
// what to do with uknown exception
}
//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp();
Sonunda herhangi bir istisna atıldığında yapılacak son şey olarak engellemek istiyorsanız, boole yerel değişkenini kullanabilirsiniz - çalıştırmadan önce false değerine ayarlayın ve try bloğunun sonuna gerçek atama koyun, sonra değişken için catch blok kontrolünden sonra değeri:
bool generalAppState = false;
try{
// something that might throw exception
//the very end of try block:
generalAppState = true;
} catch( ... ){
// what to do with uknown exception
}
//final code to be called only when exception was thrown,
//don't forget that it might throw some exception too
if( !generalAppState ){
doSomeCleanUpOfDirtyEnd();
}
//final code to be called only when no exception is thrown
//don't forget that it might throw some exception too
else{
cleanEnd();
}
Ben de RIIA istisna işleme ve nihayet sahip olmak için tamamen yararlı bir yedek olmadığını düşünüyorum. BTW, ayrıca RIIA'nın her yerde kötü bir isim olduğunu düşünüyorum. Bu tür sınıflara 'kapıcı' diyorum ve onları çok kullanıyorum. Kaynakların ne başlatıyor ne de elde edildikleri zamanın% 95'i, kapsam dahilinde bazı değişiklikler uyguluyor veya önceden kurulmuş bir şeyi alıp yok edildiğinden emin oluyorlar. Bu resmi kalıp adı takıntılı internet bile benim adımı daha iyi olabilir önermek için istismar.
Sadece geçici bir şeyler listesinin her karmaşık kurulumunun, birden çok şeyi yakalama ihtiyacı karşısında hepsini temizlerken komplikasyonları önlemek için içerdiği bir sınıfa sahip olması gerektiğini makul görmüyorum. süreçte bir şeyler ters giderse istisna türleri. Bu, başka türlü gerekli olmayacak çok sayıda özel sınıfa yol açacaktır.
Evet, belirli bir kaynağı yönetmek için tasarlanmış sınıflar veya bir dizi benzer kaynağı işlemek için tasarlanmış genel sınıflar için uygundur. Ancak, ilgili tüm şeylerin bu tür sargıları olsa bile, temizlemenin koordinasyonu sadece yıkıcıların tersine çağrılması için basit olmayabilir.
Son olarak C ++ için mükemmel bir mantıklı olduğunu düşünüyorum. Yani, jeez, son on yıllar boyunca o kadar çok bit ve bobin yapıştırıldı ki, garip insanlar aniden oldukça yararlı olabilecek ve muhtemelen diğer bazı şeyler kadar karmaşık bir şey üzerinde muhafazakâr görünecekler. (gerçi bu sadece benim açımdan bir tahmin.)
try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}
finally
da yapmıyor.