একটি বৃহত তালিকা ধ্বংস করে আমার স্ট্যাককে উপচে ফেলবে?


12

নিম্নলিখিত একক সংযুক্ত তালিকার প্রয়োগ বিবেচনা করুন:

struct node {
    std::unique_ptr<node> next;
    ComplicatedDestructorClass data;
}

এখন, ধরুন আমি std::unique_ptr<node> headএমন কোনও উদাহরণ ব্যবহার করা বন্ধ করে দিয়েছি যা এরপরে সুযোগের বাইরে চলে যায়, যার ফলে এটি ধ্বংসকারীকে ডাকা হয়।

এটি যথেষ্ট পরিমাণে বড় তালিকার জন্য আমার স্ট্যাককে আঘাত করবে? অনুমান করা কি ন্যায়সঙ্গত যে সংকলকটি একটি জটিল জটিল অপ্টিমাইজেশন করবে (ইনলাইন unique_ptrএর ডেস্ট্রাক্টর এর মধ্যে node, তারপরে পুনরাবৃত্তিটি ব্যবহার করুন), যা নিম্নলিখিতগুলি করা হলে আরও শক্ত হয়ে যায় (যেহেতু dataবিনষ্টকারী nextএটিকে শক্ত করে তোলে) সংকলকটির জন্য সম্ভাব্য পুনঃক্রম এবং টেল কলের সুযোগটি লক্ষ্য করার জন্য):

struct node {
    std::shared_ptr<node> next;
    ComplicatedDestructorClass data;
}

যদি dataকোনওভাবে এর কোনও পয়েন্টার থাকে nodeতবে এটি লেজ পুনরূদ্ধার জন্যও অসম্ভব হতে পারে (যদিও অবশ্যই এ্যাঙ্ক্যাপেসুলেশনের এ জাতীয় লঙ্ঘন এড়াতে আমাদের চেষ্টা করা উচিত)।

সাধারণভাবে, অন্যথায় কীভাবে এই তালিকাটি ধ্বংস করার কথা? আমরা তালিকাটি পেরিয়ে "বর্তমান" নোডটি মুছতে পারি না কারণ ভাগ করে নেওয়া পয়েন্টারে একটি নেই release! একমাত্র উপায় হ'ল একটি কাস্টম মোছার সাথে, যা আমার কাছে সত্যই দুর্গন্ধযুক্ত।


1
এটি কীসের জন্য উপযুক্ত, এমনকি দ্বিতীয় ক্ষেত্রে উল্লিখিত এনক্যাপসুলেশন লঙ্ঘন না করেও gcc -O3একটি পুচ্ছ পুনরাবৃত্তি (জটিল উদাহরণে) অনুকূল করতে পারেনি।
ভিএফ 1

1
সেখানে আপনার উত্তর রয়েছে: সংকলক যদি পুনরাবৃত্তিকে দূরে সরিয়ে নিতে না পারে তবে এটি আপনার স্ট্যাকটি ফুটিয়ে তুলতে পারে।
বার্ট ভ্যান ইনজেন শেহানাউ

@ বার্টওয়ানআইংজেনচেনা আমার ধারণা এই সমস্যার আর একটি উদাহরণ । এটিও সত্যিকারের লজ্জাজনক, যেহেতু আমি স্মার্ট পয়েন্টার পরিষ্কার পরিচ্ছন্নতা পছন্দ করি।
ভিএফ 1

উত্তর:


7

হ্যাঁ, এটি অবশেষে আপনার স্ট্যাকটি ফুটিয়ে তুলবে, যদি না সংকলকটি কেবলমাত্র node'ডেস্ট্রাক্টর এবং ডেস্ট্রাক্টর'এ একটি টেল কল অপ্টিমাইজেশন প্রয়োগ করে shared_ptr। দ্বিতীয়টি স্ট্যান্ডার্ড লাইব্রেরি প্রয়োগের উপর অত্যন্ত নির্ভরশীল। মাইক্রোসফ্টের এসটিএল, উদাহরণস্বরূপ, এটি কখনই করবে না, কারণ shared_ptrপ্রথমে তার পয়েন্টটির রেফারেন্স গণনা হ্রাস করে (সম্ভবত বস্তুটি ধ্বংস করে) এবং তারপরে তার নিয়ন্ত্রণ ব্লকের রেফারেন্স গণনা (দুর্বল রেফারেন্স গণনা) হ্রাস করে। সুতরাং অভ্যন্তরীণ ধ্বংসকারী কোনও লেজ কল নয়। এটি একটি ভার্চুয়াল কলও , এটি এটি অপটিমাইজড হওয়ার সম্ভাবনা আরও কম করে তোলে।

সাধারণ তালিকাগুলি পরবর্তী নোডের নিজস্ব না হয়ে, তবে সমস্ত নোডের মালিকানাধীন একটি ধারক রেখে এবং ডেস্ট্রাক্টরের সমস্ত কিছু মুছতে একটি লুপ ব্যবহার করে সমস্যাটি ঘিরে।


হ্যাঁ, আমি shared_ptrশেষ পর্যন্ত সেইগুলির জন্য একটি কাস্টম মোছার সাথে "সাধারণ" তালিকা মুছে ফেলার আলগোরিদিম প্রয়োগ করেছি । থ্রেড সুরক্ষার প্রয়োজন হওয়ায় আমি পয়েন্টারগুলি থেকে সম্পূর্ণ মুক্তি দিতে পারি না।
ভিএফ 1

আমি জানতাম না পারেন, আমি সবসময় অধিকৃত এটি শুধু একটি শুঁটি অধিষ্ঠিত শক্তিশালী refs + + দুর্বল refs + + deleter ... ছিল ভাগ পয়েন্টার "কাউন্টার" বস্তুর একটি ভার্চুয়াল বিনাশকারী হবে
VF1

@ ভিএফ 1 আপনি কি নিশ্চিত যে পয়েন্টারগুলি আপনার পছন্দ মতো থ্রেড সুরক্ষা দিচ্ছে?
সেবাস্তিয়ান রেডল

হ্যাঁ - এটাই std::atomic_*তাদের পক্ষে ওভারলোডের পুরো পয়েন্ট , না?
ভিএফ 1

হ্যাঁ, তবে এটি এমন কিছুই নয় যা আপনি std::atomic<node*>খুব বেশি এবং সস্তার সাথে অর্জন করতে পারবেন না ।
সেবাস্তিয়ান রেডল

5

দেরিতে উত্তর কিন্তু যেহেতু কেউ এটি সরবরাহ করেনি ... আমি একই সমস্যাটিতে চলে এসে কাস্টম ডেস্ট্রাক্টর ব্যবহার করে এটি সমাধান করেছি:

virtual ~node () throw () {
    while (next) {
        next = std::move(next->next);
    }
}

আপনার যদি সত্যিই একটি তালিকা থাকে , অর্থাত্ প্রতিটি নোডের আগে একটি নোড থাকে এবং তার সর্বাধিক একজন অনুগামী থাকে এবং আপনার listপ্রথমটির জন্য একটি পয়েন্টার হয় node, উপরের কাজ করা উচিত।

আপনার যদি কিছু ঝাপসা কাঠামো থাকে (যেমন অ্যাসাইক্লিক গ্রাফ), আপনি নিম্নলিখিতটি ব্যবহার করতে পারেন:

virtual ~node () throw () {
    while (next && next.use_count() < 2) {
        next = std::move(next->next);
    }
}

ধারণাটি হ'ল আপনি যখন করবেন:

next = std::move(next->next);

পুরানো ভাগ করা পয়েন্টারটি nextধ্বংস হয়ে গেছে (কারণ use_countএটি এখন 0), এবং আপনি নীচের দিকে ইঙ্গিত করেন। এটি ডিফল্ট ডেস্ট্রাক্টর ঠিক ঠিক একই কাজ করে, এটি পুনরাবৃত্তির পরিবর্তে পুনরাবৃত্তভাবে এটি করে এবং এভাবে স্ট্যাকের ওভারফ্লো এড়ায়।


আকর্ষণীয় ধারণা। এটি থ্রেড-সুরক্ষার জন্য ওপি-র প্রয়োজনীয়তা পূরণ করে তা নিশ্চিত নয়, তবে অন্যান্য বিষয়ে ইস্যুটির কাছে যাওয়ার ভাল উপায়।
জুলে

আপনি মুভ অপারেটরটিকে ওভারলোড না করলে, আমি নিশ্চিত নই যে এই পদ্ধতির আসলে কীভাবে কিছু সঞ্চয় হয় - একটি আসল তালিকায়, প্রত্যেকটি অবস্থার পুনরাবৃত্তি next = std::move(next->next)কল করার সাথে সাথে একবারে একবারে মূল্যায়ন করা হবে next->~node()
ভিএফ 1

1
@ ভিএফ 1 এটি কাজ করে কারণ next->nextদ্বারা চিহ্নিত মানটি nextনষ্ট হওয়ার আগে (মুভ অ্যাসাইনমেন্ট অপারেটর দ্বারা) অবৈধ হয়ে যায়, সুতরাং পুনরাবৃত্তিটি "থামিয়ে" দেয়। আমি আসলে এই কোড এবং এই কাজটি ব্যবহার করি (এর সাথে পরীক্ষিত g++, clangএবং msvc), তবে এখন আপনি এটি বলছেন, আমি নিশ্চিত নই যে এটি স্ট্যান্ডার্ড দ্বারা সংজ্ঞায়িত হয়েছে (পুরানো বস্তুর বিন্দু বিনষ্ট হওয়ার আগে সরানো পয়েন্টারটি অকার্যকর হয়ে গেছে লক্ষ্য পয়েন্টার দ্বারা)।
হল্ট

@ ভিএফ 1 আপডেট: মান অনুসারে, operator=(std::shared_ptr&& r)সমান std::shared_ptr(std::move(r)).swap(*this)। এখনও স্ট্যান্ডার্ড থেকে, এর মুভ কনস্ট্রাক্টর খালি করে std::shared_ptr(std::shared_ptr&& r)তোলে r, এভাবে কল করার আগে rখালি ( r.get() == nullptr) swap। আমার ক্ষেত্রে, next->nextপুরানো অবজেক্টটি nextবিনষ্ট হওয়ার আগে ( swapকল দিয়ে) এটির অর্থ খালি ।
হল্ট

1
@ ভিএফ 1 আপনার কোডটি এক নয় - কল করার সময়টি fচলছে next, না next->nextএবং যেহেতু next->nextশূন্য, তাই এটি অবিলম্বে বন্ধ হয়ে যায়।
হল্ট

1

সত্যি কথা বলতে আমি কোনও সি ++ কম্পাইলারের স্মার্ট পয়েন্টার ডিএলোকেশন অ্যালগরিদমের সাথে পরিচিত নই, তবে আমি এটি একটি সাধারণ, পুনরাবৃত্ত অ্যালগরিদম কল্পনা করতে পারি যা এটি করে। এই বিবেচনা:

  • আপনার কাছে স্মরণ পয়েন্টারগুলির একটি সারি রয়েছে ডিওলোকেশনের জন্য অপেক্ষা করছে।
  • আপনার একটি ফাংশন রয়েছে যা প্রথম পয়েন্টারটি নেয় এবং এটিকে deallocates করে এবং সারিটি খালি না হওয়া পর্যন্ত এটি পুনরাবৃত্তি করে।
  • যদি কোনও স্মার্ট পয়েন্টারটির অবনতির প্রয়োজন হয় তবে এটি কাতারে ধাক্কা খায় এবং উপরের ফাংশনটি কল হয়ে যায়।

সুতরাং স্ট্যাকের উপচে পড়ার কোনও সুযোগ থাকবে না এবং এটি পুনরাবৃত্ত আলগোরিদিমকে অনুকূলকরণ করা অনেক সহজ।

এটি "প্রায় শূন্য দামের স্মার্ট পয়েন্টার" দর্শনে ফিট করে কিনা আমি নিশ্চিত নই।

আমি অনুমান করব যে আপনি যা বর্ণনা করেছেন তা স্ট্যাকের উপচে পড়বে না, তবে আপনি আমাকে ভুল প্রমাণ করার জন্য একটি চতুর পরীক্ষা তৈরির চেষ্টা করতে পারেন।

হালনাগাদ

ঠিক আছে, এটি আগে যা লিখেছিলাম তা ভুল প্রমাণ করে:

#include <iostream>
#include <memory>

using namespace std;

class Node;

Node *last;
long i;

class Node
{
public:
   unique_ptr<Node> next;
   ~Node()
   {
     last->next.reset(new Node);
     last = last->next.get();
     cout << i++ << endl;
   }
};

void ignite()
{
    Node n;
    n.next.reset(new Node);
    last = n.next.get();
}

int main()
{
    i = 0;
    ignite();
    return 0;
}

এই প্রোগ্রামটি চিরন্তন নোডগুলির শৃঙ্খলা তৈরি করে এবং ডিকনস্ট্রাক্ট করে। এটি স্ট্যাকের অতিরিক্ত প্রবাহকে কারণ করে।


1
আহ, আপনি ধারাবাহিকতা-পাসিং শৈলী ব্যবহার করতে চান? কার্যকরভাবে, এটি আপনি বর্ণনা করছেন। যাইহোক, আমি শীঘ্রই স্মার্ট পয়েন্টারকে ত্যাগ করতে চাইছি কেবল কোনও পুরানোটিকে অবনমিত করার জন্য গাদাতে আরও একটি তালিকা তৈরি করা
ভিএফ 1

আমি ভৃল ছিলাম. আমি সেই অনুযায়ী আমার উত্তর পরিবর্তন করেছি।
গ্যাবার অ্যাঙ্গিয়াল
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.